How to run a SQL Plus script in PowerShell - sql

I am trying to log in to the the Oracle DB using PowerShell and run a script called "C:\Users\Administrator\Desktop\oracle\OracleCleanTest.sql", When I execute the PS nothing happens.
Here is what I have.
$adminLogon = "sys as sysdba/manager#ORCL"
$logon = "sqlplus\sql/manager#ORCL"
$mydata = Invoke-SqlPlus -inputfile "#C:\Users\Administrator\Desktop\oracle\OracleCleanTest.sql" $logon
I've also tried this.
$database = "ORCL";
$user = "sys as sysdba";
$pw = "manager";
sqlplus.exe -d $database -U $user -P $pw -I "#C:\Users\Administrator\Desktop\oracle\OracleCleanTest.sql"
I tried this.
& 'C:\app\Administrator\product\11.2.0\client_1\BIN\sqlplus.exe' 'QE-JDBC-1/manager#ORCL sys as sysdba' '#C:\Users\Administrator\Desktop\oracle\OracleCleanTest.sql'
I get the error, "& : The module 'sqlplus' could not be loaded. For more information, run 'Import-Module sqlplus'.
At line:5 char:3
+ & $mydata Invoke-SqlPlus -inputfile "#C:\Users\Administrator\Desktop\oracle\Orac ...
+ ~~~~~~~
+ CategoryInfo : ObjectNotFound: (sqlplus\sql/manager#ORCL:String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : CouldNotAutoLoadModule"

I use the call operator, &, as Keith Hill has suggested with the question, How to run an EXE file in PowerShell with parameters with spaces and quotes.
& 'path\sqlplus.exe' 'system/password#dbase as sysdba'
I placed the username, password in quotes due to the spaces.
To start a script, I add another parameter as follows:
& 'path\sqlplus.exe' 'system/password#dbase as sysdba' '#my_script.sql'
If you are receiving the ORA-12154 error, and you know that other users have
established connections (which implies that the database listener is running
properly); I would then examine if SQL*Plus can find my tnsname file.
My first task would be to see if I can tnsping as follows in Windows cmd.exe:
tnsping orcl
It will confirm that a connection can (or can not be established).
If it cannot, I would check to see if the environment variable, ORACLE_HOME,
is set. SQL*Plus uses this to find tnsname.ora file.
If it is not set, I would execute this statement in PowerShell (to establish
this environment variable):
[Environment]::SetEnvironmentVariable("ORACLE_HOME", "C:\app\Administrator\product\11.2.0\client_1" , "User")
Next, I would retry to tnsping (identified above).
Once successful, I would re-try to execute the script running command above.

I use this:
$cmd = "cmd.exe"
$args = ("/c sqlplus {0}/{1}#{2}:{3}/{4} #{5} {6}" -f $userName, $password, $tnsAlias, $port, $dbInstance, $sqlScript, $outputFileName)
&$cmd $args

In your Windows PowerShell command prompt the code does not require variable setting or anything fancy. Just do this:
sqlplus ElBankoUser\SupaSecretyPass "#C:\Users\ElBankoUser\Documents\MaFancySckrp.sql"

You can use .NET Oracle library DLL, just make sure you have the required DLL file under the lib folder
Add-Type -Path "lib\Oracle.ManagedDataAccess.dll"
$query = "select 1 as Col1, 2 as Col2, 3 as Col3 from dual
union
select 4 as Col1, 5 as Col2, 6 as Col3 from dual
union
select 7 as Col1, 8 as Col2, 9 as Col3 from dual"
$cn = New-Object Oracle.ManagedDataAccess.Client.OracleConnection -ArgumentList "TNS-ConnectionString-Here"
$cmd = New-Object Oracle.ManagedDataAccess.Client.OracleCommand -ArgumentList $query
$cmd.Connection = $cn
try {
$cn.Open()
$reader = $cmd.ExecuteReader()
while ($reader.Read()) {
$col1 = $reader["Col1"]
$col2 = $reader["Col2"]
$col3 = $reader["Col3"]
Write-Host $col1, $col2, $col3
}
} catch {
Write-Error $_.Exception.Message
} finally {
$cmd.Dispose()
$cn.Dispose()
}

Why not use this?
sqlplus -s $adminLogin "#C:\Users\Administrator\Desktop\oracle\OracleCleanTest.sql"
-s just suppresses the sqlplus banner.

Related

How doI use Powershell to take output from a SQL query and search another file for that output

This is my first time using Powershell so please forgive my ignorance.
I have a SQL query that returns back a bunch of order numbers. I want to check another file to see if there is an existing PDF in that file with the same name as the orders numbers returned by the SQL query.
Everything in my code works up until the ForEach loop which returns nothing. Based on my google searches I think I'm pretty close but I'm not sure what I'm doing wrong. Any help would be appreciated.
I've removed the actual file name for obvious reasons, and I do know that the file is correct and other commands do access it so I know that is not my problem at the moment. I've also removed sensitive info from the SQL query.
$statement = "SELECT A, Date FROM XXXX
WHERE STAT = 1 AND Date >= trunc(sysdate)"
$con = New-Object System.Data.OracleClient.OracleConnection($connection_string)
$con.Open()
$cmd = $con.CreateCommand()
$cmd.CommandText = $statement
$result = $cmd.ExecuteReader()
$list = while ($result.Read()) { $result["A"]}
Write-Output $list​
#########Loop through the list above here to check for matching PDF
ForEach ($Order in $list){
Get-ChildItem "\\xxxxxx\" -Filter $Order -File
#########If FALSE - notify that PDF is missing
}
$con.close()
I have also tried the following code, which gets me closer and actually finds the files I'm looking for, but gives the error
" Get-ChildItem : A positional parameter cannot be found that accepts argument
CategoryInfo : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException
FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand"
ForEach ($Order in $list){
if((Get-ChildItem "\\xxxxx\" + $Order)){
Write-Output
} else { Write-Host "Does not exist."}
I gather from your comment that $list is an array of order numbers.
Next, you want to check if there is a file in a folder having that name, correct?
Then I'd suggest you use Test-Path instead of Get-ChildItem:
$folderToSearch = '\\servername\sharename\folder'
foreach ($Order in $list) {
$fileToCheck = Join-Path -Path $folderToSearch -ChildPath ('{0}.pdf' -f $Order)
if (Test-Path -Path $fileToCheck -PathType Leaf) {
"File found: $fileToCheck"
}
else {
"File $fileToCheck does not exist"
}
}

Oracle Sql and Powershell : Execute query, print/output results

I need a way to execute a SQL (by importing a .SQL script) on a remote Oracle DB using PowerShell. In addition to this I am also trying to output the results in an .xls format in a desired folder location. To add to the fun, I would also want to run this task on an automatic schedule. Please help !
I have gotten so far :
[System.Reflection.Assembly]::LoadWithPartialName ("System.Data.OracleClient") | Out-Null
$connection = "my TNS entry"
$queryString = "my SQL query"
$command = new-Object System.Data.OracleClient.OracleCommand($queryString, $connection)
$connection.Open()
$reader = $command.ExecuteReader()
$tempArr = #()
#read all rows into a hash table
while ($reader.Read())
{
$row = #{}
for ($i = 0; $i -lt $reader.FieldCount; $i++)
{
$row[$reader.GetName($i)] = $reader.GetValue($i)
}
#convert hashtable into an array of PSObjects
$tempArr+= new-object psobject -property $row
}
$connection.Close()
write-host "Conn State--> " $connection.State
$tmpArr | Export-Csv "my File Path" -NoTypeInformation
$Error[0] | fl -Force
The easiest way is to drive sqlplus.exe via powershell. To execute the sql and get the output you do this:
$result = sqlplus.exe #file.sql [credentials/server]
#parse result into CSV here which can be loaded into excel
You can schedule this script with something like:
schtasks.exe /create /TN sqlplus /TR "Powershell -File script.ps1" /ST 10 ...
For this you need to have sqlplus installed (it comes with oracle express and you could install it without it). This obviously introduces dependency that is not needed but sqlplus could be used to examine the database and do any kind of thing which might be good thing to have around.

Running a script from a powershell script with conditions

So, I have a script I use for deployments and some of these commands aren't recognized till after sqlps is run, usually I do it manually. I want to automate the running of that script. Here is the script:
$client = Read-Host "Enter Client name"
$date = Get-Date -Format "yymmdd"
$sqlsrvname = Read-Host "Please enter the sql server name"
$deploytype = Read-Host "Is there a server instance? (1) Yes (2) No"
switch($deploytype){
1 {$Instance = Read-Host "Please Enter instance name"
cd -Path $ppath
Invoke-Command .\sqlpatchremote.ps1 -DBServer $sqlsrvname –dbinstance $Instance –client $client –mainline HTFS –datefolder $date –targetenv $sqlsrvname }
2 {cd -Path $ppath
Invoke-Command .\sqlpatchremote.ps1 –dbs $sqlsrvname –client $client –mainline HTFS –datefolder $date –targetenv $sqlsrvname }
default {"Invalid Selection"}
}
When I try to run this script I get this error:
Invoke-Command : A parameter cannot be found that matches parameter name 'DBServer'.
At line:17 char:38
+ Invoke-Command .\sqlpatchremote.ps1 -DBServer $sqlsrvname –dbinstance $Instance
...
+ ~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-Command], ParameterBindi
ngException
+ FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.
InvokeCommandCommand
It tells me its an invalid command when it normally works when I type this in manually, how can I make this work? This script is suppose to install SQL databases on a SQL server. When I run this code manually I just type "sqlps" and then navigate to the directory of the script. Then I run it with the filled in parameters and it doesn't give me an error. I feel like this might be a simple fix to get this to work, but I'm not sure what it is and I wasn't really sure how to ask it. Thanks!
Error:
Invoke-Command : A parameter cannot be found that matches parameter name 'DBServer'.
At line:17 char:38
Invoke-Command rejects parameter DBServer. This means you are passing the arguments to Invoke-Command instead of your script.
To pass the arguments to the script you are invoking, you have to use the -ArgumentList parameter.
Try :
Invoke-Command -ComputerName "targethost" .\sqlpatchremote.ps1 -ArgumentList "-DBServer $sqlsrvname –dbinstance $Instance –client $client –mainline HTFS –datefolder $date –targetenv $sqlsrvname"
EDIT: really not sure about the above syntax for the arguments :( (if anyone could confirm?)
With the arguments in the proper order I've successfully tested it like this:
Invoke-Command -ComputerName "targethost" "scriptpath" -ArgumentList $arg1,$arg2#,...
MSDN

Take a result set (eg. listofcomputers.txt) and run a copy-item command on every item of result set

Hi all: This is what I'm trying to do....
Take a result set (eg. listoffailedcomputers.txt) and run a copy command on every item inside the result set.
The logic is to run a copy command on all the computers in that failedlistofcomputers.txt so that the end result will have that folder copied down locally on all computers on that list.
I can do this by using a remote console on all those computers but that would not be efficient.
Thank You.
Here is the code that I wrote so far.......
$failedcomputers = gc c:\listoffailedcomputers.txt
foreach ($failedcomputer in $failedcomputers)
{
$failedcomputer | copy-item \\server\patch\*.* -destination c:\patch\
}
And this is the error I'm getting......
Copy-Item : The input object cannot be bound to any parameters for the command
either because the command does not take pipeline input or the input and its
properties do not match any of the parameters that take pipeline input.
At copypatchtofailedcomputers.ps
+ $failedcomputer | copy-item <<<< \\server\patch\ -destination c:\patch\
+ CategoryInfo : InvalidArgument: (mycomputername:PSObject) [Copy-
Item], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Command
s.CopyItemCommand
If I remove the pipe between the $failedcomputer variable and the copy-item command on my statement, I'll get a unexpected token error.
You can't just pipe a computername into any cmdlet and expect it to understand how to use it. Copy-Item doesn't even include a -ComputerName parameter. You could try two approaches
You could remote execute copy-item on each computer, like this:
$failedcomputers = gc c:\listoffailedcomputers.txt
foreach ($failedcomputer in $failedcomputers)
{
Invoke-Command -ComputerName $failedcomputer -ScriptBlock { copy-item \\server\patch\*.* -destination c:\patch\ }
}
Or, if you have file access to all remote computers from your computer, you could try to copy directly from one network share to another(the destination computer), like this:
$failedcomputers = gc c:\listoffailedcomputers.txt
foreach ($failedcomputer in $failedcomputers)
{
copy-item \\server\patch\*.* -destination "\\$failedcomputer\c$\patch\"
}
Another possibility:
$failedcomputers = gc c:\listoffailedcomputers.txt
$CmdParams =
#{
ClassName = 'Win32_Process'
MethodName = 'Create'
Arguments = #{ CommandLine = 'copy \\server\patch\*.* c:\patch\' }
}
Invoke-CimMethod #CmdParams -ComputerName $failedcomputers
That should multi-thread it, without the overhead of spinning up a bunch of remote PS instances just to do a file copy.
If you look at the Get-Help Copy-Item -full in ISE it tells you what it can accept on the pipeline. You can pipe a string that contains a path to Copy-ItemProperty. You are actually piping a hostname in this instance, which is why you are getting that error.
Try this:
copy-item \\server\patch\*.* -destination \\$failedcomputer\C$\patch

SQLCMD, when called from Powershell, returns ExitCode == 1, even if successful

I have a Powershell script that calls sqlcmd to run a sql script that backs up a database:
function RunSqlScriptWithErrorMessage
{
param([string]$sqlServer, [string]$sqlUserName, [string]$sqlPassword, [string]$scriptName, [string]$errorMessage)
$commandName = "sqlcmd"
$startInfo = New-Object Diagnostics.ProcessStartInfo($commandName)
$startInfo.UseShellExecute = $false
$startInfo.Arguments = "-S $sqlServer -U $sqlUserName -P $sqlPassword -i `"${sqlScriptName}`""
$process = [Diagnostics.Process]::Start($startInfo)
$process.WaitForExit()
$exitCode = $process.ExitCode
if($exitCode -ne 0 ) { throw $errorMessage}
}
The strange thing is that process.ExitCode == 1 even though the back up is successful. Any ideas why this would be - shouldn't the exitcode be 0 if successful?
Thanks!
Sorry - it was an error.