Home > Software engineering >  Try/Catch not working with a terminating error; script hangs
Try/Catch not working with a terminating error; script hangs

Time:10-25

I'm trying to gracefully exit the program if the username/password is incorrect or (ORA-01017) but the script hangs when getting an error. My proposed error is in the try section of the code then the entire script hangs. I thought the catch is suppose to throw the exception. Ideally I will create a for loop that will connect to each database and if there is an error just capture the error and go on to the next. But right now just testing the try/catching the error option. Also, I'm encrypting the password. Any ideas or suggestions

enter code here

function Start-Something
{
    $User = Read-Host -Prompt 'Input the Oracle database user name'
    $psswd =  Read-Host -Prompt 'Input Oracle database Password:' -AsSecureString
    $cryptpasswd = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($psswd))
    Write-Host "###############################################"
    Write-Host Connecting to Oracle Database: testdb -ForegroundColor Green
    sqlplus -s $User/$cryptpasswd@$line '@C:\Users\my\Documents\Scripts\oracledbinfo.sql'
    Write-Host "###############################################"
    Write-Host " ###########################################"
}

try
{
    Start-Something
 }
catch
{
    Write-Output "Something threw an exception"
   Write-Output $_
 }   

try { Start-Something -ErrorAction Stop } catch { Write-Output "Something threw an exception or used Write-Error" Write-Output $_ }

CodePudding user response:

Try/Catch does not hang. Looks like this line of code is waiting for input:

 sqlplus -s $User/$cryptpasswd@$line '@C:\Users\my\Documents\Scripts\oracledbinfo.sql'

To verify run the codeblock of the function directly in the shell.

Currently you ask for the password and convert the string entered to a secureString Read-Host -Prompt 'Input Oracle database Password:' -AsSecureString then you probably try to convert the value back to string, because sqlplus won't accept a secureString. But you missed a part =(System.Runtime.InteropServices.Marshal]::PtrToStringAuto())...

Anyways, here are some tips for you:

Take a look at the oracle .NET provider its the better choice on PowerShell.

If you want the user to input credentials use the get-credential cmdlet. By doing so you get a credential object back which you can use to authenticate or by calling the method getNetworkCredential() to get the password in cleartext.

Implement also a try/catch block within the function and return errors always with write-error and learn about output streams. try/Catch will only consider terminating errors, but sqlplus won't give you that -> oracle .NET provider.

CodePudding user response:

Preface:

  • Toni's answer makes a good point that the apparent hang may be due to sqlplus waiting for an interactive response. If that is the case, that problem must be solved first.

  • A simpler way to convert a SecureString instance to plain text - which notably defeats the (limited) security benefits that SecureString provides (on Windows only) - is the following:

    [pscredential]::new('unused', $psswd).GetNetworkCredential().Password
    
    • As an inconsequential aside, with respect to your approach: it should be [Runtime.InteropServices.Marshal]::PtrToStringBSTR(...), not [Runtime.InteropServices.Marshal]::PtrToStringAuto(...)

  • As of PowerShell 7.2.x, calls to external programs (such as sqlplus) never cause a (statement-)terminating error[1] that you can trap with try / catch

    • A potential future feature - which in preview versions of v7.3 is available as an experimental feature that may or may not become official - may allow better integration with PowerShell's error handling, by setting the $PSNativeCommandUseErrorActionPreference preference variable to $true.
      • Unfortunately, as of v7.3.0-preview.8, the error that is reported in response to a nonzero exit code (see below) is unexpectedly a non-terminating error, which means it can not be trapped with try / catch by the caller - see GitHub issue #18368.
  • To determine if a call to an external program call failed, examine its process exit code, as reflected in the automatic $LASTEXITCODE variable afterwards.

    • By convention, exit code 0 signals success, any nonzero value failure.

Therefore:

# Note: Write-Host calls omitted for brevity.
function Start-Something {

  $User = Read-Host -Prompt 'Input the Oracle database user name'
  $psswd = Read-Host -Prompt 'Input Oracle database Password:' -AsSecureString
  $psswdClearText = [pscredential]::new('unused', $psswd).GetNetworkCredential().Password

  # Invoke sqlplus.
  # !! AS NOTED, YOU MAY HAVE TO MODIFY THIS COMMAND TO PREVENT AN INTERACTIVE PROMPT.
  # Stderr output from this call, if any, goes directly to the display.
  sqlplus -s $User/$psswdClearText@$line '@C:\Users\my\Documents\Scripts\oracledbinfo.sql'

  # If sqlplus reported a nonzero exit code, 
  # throw a script-terminating error that the caller must handle with try / catch.
  if ($LASTEXITCODE -ne 0) {
    throw "sqlplus signaled failure: exit code is: $LASTEXITCODE"
  }

}

try {
  Start-Something
}
catch {
  # Convert the script-terminating error into a non-terminating one.
  $_ | Write-Error
}

[1] There are only two exceptions: (a) If the external program cannot even be invoked, e.g., because its name/path is misspelled or it isn't installed. Or (b), due to a bug, present up to PowerShell 7.1, where the combination of redirecting stderr output (2> or *>) with $ErrorActionPreference = 'Stop' unexpectedly caused a script-terminating error if there's actual stderr output - see this answer.

  • Related