Data inserting to ODBC destination with powershell - sql

I need to load data table to ODBC driver connection with powershell.
With OLEDB and SQL server we can use Bulk Copy and insert data quickly.
Is there such posibility with ODBC ?
I'm using powershell because it shoud have the best support for these kind of opperations,
but my current code doesn't utillise an of the dlls.
So my code firstly needs to create an insert statements with two for loops and iterate on every row and hold it in its memory,
and then to construct INSERT INTO with 1000 rows, and then repeat same thing.
Am i doomed to something like this ?
$Datatable = New-Object System.Data.DataTable
$tabledump= $src_cmd.ExecuteReader()
$Datatable.Load($tabledump)
foreach ($item in $Datatable.Rows) {
$f +=1
for ($i = 0; $i -lt $item.ItemArray.Length; $i++) {
$items = $item[$i] -replace "'" , "''"
$val +="'"+ $items + "',"
}
$vals += $val
if ($f % 1000 -eq 0 -or $f -eq $row_cnt) {
$values = [system.String]::Join(" ", $vals)
$values = $values.TrimEnd(",")
$cols = [system.String]::Join(",", $columns)
$postgresCommand = "Insert Into $dst_schema.$dst_table ($cols) values $values"
$dest_cmd_.CommandText = $postgresCommand
$dest_cmd_.ExecuteNonQuery()
Bad code i admit, any advice on code compositions are welcomed.

You can use Get-ODBCDSN command to retrieve the values of the ODBC connections and use it with a query
$conn.ConnectionString= "DSN=$dsn;"
$cmd = new-object System.Data.Odbc.OdbcCommand($query,$conn)
$conn.open()
$cmd.ExecuteNonQuery()
$conn.close()
https://www.andersrodland.com/working-with-odbc-connections-in-powershell/
But the ODBC provider doesnt do bulk copy
https://learn.microsoft.com/en-us/sql/relational-databases/native-client-odbc-bulk-copy-operations/performing-bulk-copy-operations-odbc?view=sql-server-ver15

I know this post is not new, but i've been fiddeling around looking for a solution and also found nothing, however this post gave me a couple of insights.
First: There is no such thing as 'Bad Code'. If it works is not bad, heck even if it didn't worked, but helped with something..
Alright, what i did is not the best solution, but i'm trying to import Active Directory data on PostgreSQL, so...
I noticed that you're trying with pgsql as well, so you can use the COPY statement.
https://www.postgresql.org/docs/9.2/sql-copy.html
https://www.postgresqltutorial.com/import-csv-file-into-posgresql-table/
In my case i used it with a csv file:
*Assuming you have installed pgsql ODBC driver
$DBConn = New-Object System.Data.Odbc.OdbcConnection
$DBConnectionString = "Driver={PostgreSQL UNICODE(x64)};Server=$ServerInstance;Port=$Port;Database=$Database;Uid=$Username;Pwd=$(ConvertFrom-SecureString -SecureString $Password);"
$DBConn.ConnectionString = $DBConnectionString
try
{
$ADFObject = #()
$ADComputers = Get-ADComputer -Filter * -SearchBase "OU=Some,OU=OrgU,OU=On,DC=Domain,DC=com" -Properties Description,DistinguishedName,Enabled,LastLogonTimestamp,modifyTimestamp,Name,ObjectGUID | Select-Object Description,DistinguishedName,Enabled,LastLogonTimestamp,modifyTimestamp,Name,ObjectGUID
foreach ($ADComputer in $ADComputers) {
switch ($ADComputer.Enabled) {
$true {
$ADEnabled = 1
}
$false {
$ADEnabled = 0
}
}
$ADFObject += [PSCustomObject] #{
ADName = $ADComputer.Name
ADInsert_Time = Get-Date
ADEnabled = $ADEnabled
ADDistinguishedName = $ADComputer.DistinguishedName
ADObjectGUID = $ADComputer.ObjectGUID
ADLastLogonTimestamp = [datetime]::FromFileTime($ADComputer.LastLogonTimestamp)
ADModifyTimestamp = $ADComputer.modifyTimestamp
ADDescription = $ADComputer.Description
}
}
$ADFObject | Export-Csv $Env:TEMP\TempPsAd.csv -Delimiter ',' -NoTypeInformation
docker cp $Env:TEMP\TempPsAd.csv postgres_docker:/media/TempPsAd.csv
$DBConn.Open()
$DBCmd = $DBConn.CreateCommand()
$DBCmd.CommandText = #"
COPY AD_Devices (ADName,ADInsert_Time,ADEnabled,ADDistinguishedName,ADObjectGUID,ADLastLogonTimestamp,ADModifyTimestamp,ADDescription)
FROM '/media/TempPsAd.csv'
DELIMITER ','
CSV HEADER
"#
$DBCmd.ExecuteReader()
$DBConn.Close()
docker exec postgres_docker rm -rf /media/TempPsAd.csv
Remove-Item $Env:TEMP\TempPsAd.csv -Force
}
catch
{
Write-Error "$($_.Exception.Message)"
continue
}
Hope it helps!
Cheers!

Related

is there a way to capture a exact error message in ps catch block?

is there any way to capture specific error message while this call to store that error message in sql table ?
function Get-SqlData {
param([string]$serverName=$(throw 'serverName is required.'), [string]$databaseName=$(throw 'databaseName is required.'),
[string]$query=$(throw 'query is required.'))
try {
Write-Verbose "Get-SqlData serverName:$serverName databaseName:$databaseName query:$query"
$connection = new-object system.data.sqlclient.sqlconnection( "Data Source=$serverName;Initial Catalog=$databaseName;Integrated Security=SSPI;")
$adapter = new-object system.data.sqlclient.sqldataadapter ($query, $connection)
$table = new-object system.data.datatable
[void]$adapter.Fill($table) #| out-null
$table
} catch {
write-host $Server
write-host 'Connection issue'
}
}
$Query = "set nocount on; SELECT CASE WHEN Is_Clustered = 1 THEN SQLClusterName ELSE ServerName END FROM Server_Master_List WHERE Is_Monitored = 1 "
$Servers = sqlcmd -b -S XYZ-XYZ -d DBA -h -1 -Q $Query -W
$sqltbl = #()
foreach($Server in $Servers) { $sqltbl += Get-SqlData $Server 'master' $qry }
#$sqltbl
<#Insert data from Powershell variable to SQL table #>
$connectionString = "Server=$env:ComputerName;Database=DBA;Integrated Security=SSPI;"
Yes. You can tell PowerShell to only catch certain types of exceptions.
For example...
$serverName = 'SOMERANDOMSERVER'
$databaseName = 'DoesntMatter'
$query = 'SELECT 1'
try {
$connection = New-Object System.Data.SqlClient.SqlConnection ("Data Source=$serverName;Initial Catalog=$databaseName;Integrated Security=SSPI;")
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter ($query, $connection)
$table = New-Object System.Data.DataTable
[void]$adapter.Fill($table)
$table
} catch [System.Data.SqlClient.SqlException] {
'CAUGHT A SQL EXCEPTION!!'
} catch {
'Caught some other type of exception'
}
However, if you want to get further into the details, you'll need to start parsing the exceptions themselves.
And that's where this leads me to ask...why do you need to do this? A query with bad syntax, a query that throws an error, an unavailable server...those will all return a SqlException. Do you plan on implementing something which handles each of these exceptions in a particular way?
Personal opinion:
Any time I see someone starting to write code in PowerShell for running SQL queries, my first question is...Are you trying to build some sort of maintenance/utility script where it's okay to utilize existing community modules? If so, you need to look up dbatools. It's a PowerShell module that is packed with cmdlets that handle all this stuff for you. For example, you've basically just written their cmdlet called Invoke-DbaQuery
Another tip...learn about advanced parameters in PowerShell. You can add various checks against parameters to ensure they are mandatory, and even include verification checks to ensure the parameter values are valid prior to executing the script. That would allow you to properly implement required parameters, and you can remove the hack you've used here.

write data into excel using powershell from sql table

I have a .xlsx file which was made into data table by oledb provider.Now I want to add value to that .xlsx based on the sql table data I have
(which is also converted into a csv file Book1.csv)
The sql table consists of name and notes...
Where name column is same in both .xlsx file and sql variable $sql
I want to add that close notes to f column of .xlsx file if the value of name matches with the value of sql table "A" column One I wrote below is very slow and not effective.
Any help would be highly appreciated.
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open('C:\Users\VIKRAM\Documents\Sample - Superstore.xlsx')
$workSheet = $Workbook.Sheets.Item(1)
$WorkSheet.Name
$Found = $WorkSheet.Cells.Find('$Data.number')
$Found.row
$Found.text
$Excel1 = New-Object -ComObject Excel.Application
$file = $Excel1.Workbooks.Open('C:\Users\VIKRAM\Documents\Book1.xlsx')
$ff=$file.Sheets.Item(1)
$ff.Name
$ff1=$ff.Range("A1").entirecolumn
$ff1.Value2
foreach ($line in $ff1.value2){
if( $found.text -eq $line)
{
Write-Host "success"
$fff=$ff1.Row
$WorkSheet.Cells.item($fff,20) =$ff.cells.item($fff,2)
}
}
Data in .xlsx file
Number Priority Comment
612721 4 - High
Data in Book1.csv
Number Clo_notes
612721 Order has been closed
I need to update clo_notes value to comment in .xlsx file if this "number" column in each file matches update the clos_notes to the corresponding column of comment
It looks like you answered my question about where "Nebraska" falls into the data.
Make sure to release any COM objects, or you'll have orphaned Excel processes.
You might try something like this. I was able to write the Clo_notes value into column 6 as you were requesting:
## function to close all com objects
function Release-Ref ($ref) {
([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
## open Excel data
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open('C:\Users\51290\Documents\_temp\StackOverflowAnswers\Excel.xlsx')
$workSheet = $Workbook.Sheets.Item(1)
$WorkSheet.Name
## open SQL data
$Excel1 = New-Object -ComObject Excel.Application
$file = $Excel1.Workbooks.Open('C:\Users\51290\Documents\_temp\StackOverflowAnswers\SQL.xlsx')
$sheetSQL = $file.Sheets.Item(1)
$dataSQL = $sheetSQL.Range("A1").currentregion
$foundNumber = 0
$row_idx = 1
foreach ($row in $WorkSheet.Rows) {
"row_idx = " + $row_idx
if ($row_idx -gt 1) {
$foundNumber = $row.Cells.Item(1,1).Value2
"foundNumber = " + $foundNumber
if ($foundNumber -eq "" -or $foundNumber -eq $null) {
Break
}
foreach ($cell in $dataSQL.Cells) {
if ($cell.Row -gt 1) {
if ($cell.Column -eq 1 -and $cell.Value2 -eq $foundNumber) {
$clo_notes = $sheetSQL.Cells.Item($cell.Row, 2).Value2
Write-Host "success"
$WorkSheet.Cells.item($row_idx, 6).Value2 = $clo_notes
}
}
}
}
$row_idx++
}
$Excel.Quit()
$Excel1.Quit()
## close all object references
Release-Ref($WorkSheet)
Release-Ref($WorkBook)
Release-Ref($Excel)
Release-Ref($Excel1)

execution time of PowerShell script vastly differs between first and second run

I'm facing something really strange when executing a little PowerShell script that fetches some information of a local Oralce DB and put the results into objects. The first execution of this scipt takes about 30 seconds, consecutive executions just as much as 2 seconds!
Does anybody have an explanation for this? (is it caching in the Oracle-DB? / any way to figure out what is causing the speed burst on the second run?)
function Get-SQLConnection
{
Add-Type -Path $(Join-Path $PSScriptRoot "libs/Oracle.ManagedDataAccess.dll")
$connectionString = 'User Id=FOO;Password=BAR;Data Source=TNSCONNID'
return New-Object Oracle.ManagedDataAccess.Client.OracleConnection($connectionString)
}
function Get-SQLIssueQuery($version)
{
$version = $version.ToString()
return "SELECT ...
FROM ... WHERE $version ..."
}
function Get-IssuesForVersion($dbconn, $version)
{
Write-Verbose "Get-IssuesForVersion $($version.ToString())..."
$issues = #{}
$command=$dbconn.CreateCommand()
$command.CommandText=$(Get-SQLIssueQuery $version)
$reader=$command.ExecuteReader()
while ($reader.Read()) {
$text = $reader[0]
[int]$key = $reader[2].Split("-")[1]
$type = $reader[6]
...
$issues[$key] = [Issue]::new($key, $type, ...)
}
Write-KSDetail "Get-IssuesForVersion '$($version.ToString())' returned $($issues.Count) issues..."
return $issues
}
$timestart = $(Get-Date)
$conn = Get-SQLConnection
$conn.open()
Get-IssuesForVersion $conn "Version XYZ"
$conn.close()
$timeend = $(Get-Date)
$duration = $timeend - $timestart # first run: approx 30 seconds
$timestart = $(Get-Date)
$conn = Get-SQLConnection
$conn.open()
Get-IssuesForVersion $conn "Version XYZ"
$conn.close()
$timeend = $(Get-Date)
$duration = $timeend - $timestart # second run: approx 2 seconds
thanks in advance!

PowerShell Script DB Query not passing all results

Good morning stackoverflow. I have a PowerShell script that is executing a SQL query against an Oracle database and then taking the results and passing them to a local shell command. It works, mostly. What is happening is some of the results are being dropped and the only significance I can see about these is that they have a couple of columns that have null values (but only 2 out of the 8 columns that are being returned). When the query is executed in sQL developer I get all expected results. This issue applies to the $eventcheck switch, the $statuscheck works fine. The Powershell script is below:
param(
[parameter(mandatory=$True)]$username,
[parameter(mandatory=$True)]$password,
$paramreport,
$paramsite,
[switch]$eventcheck,
[switch]$statuscheck
)
$qry1 = Get-Content .\vantageplus_processing.sql
$qry2 = #"
select max(TO_CHAR(VP_ACTUAL_RPT_DETAILS.ETLLOADER_OUT,'YYYYMMDDHH24MISS')) Completed
from MONITOR.VP_ACTUAL_RPT_DETAILS
where VP_ACTUAL_RPT_DETAILS.REPORTNUMBER = '$($paramreport)' and VP_ACTUAL_RPT_DETAILS.SITE_NAME = '$($paramsite)'
order by completed desc
"#
$connString = #"
Provider=OraOLEDB.Oracle;Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST="HOST")(PORT="1521"))
(CONNECT_DATA=(SERVICE_NAME="SERVICE")));User ID="$username";Password="$password"
"#
function Get-OLEDBData ($connectstring, $sql) {
$OLEDBConn = New-Object System.Data.OleDb.OleDbConnection($connectstring)
$OLEDBConn.open()
$readcmd = New-Object system.Data.OleDb.OleDbCommand($sql,$OLEDBConn)
$readcmd.CommandTimeout = '300'
$da = New-Object system.Data.OleDb.OleDbDataAdapter($readcmd)
$dt = New-Object System.Data.DataTable
[void]$da.fill($dt)
$OLEDBConn.close()
return $dt
}
if ($eventcheck)
{
$output = Get-OLEDBData $connString $qry1
ForEach ($lines in $output)
{
start-process -NoNewWindow -FilePath msend.exe -ArgumentList #"
-n bem_snmp01 -r CRITICAL -a CSG_VANTAGE_PLUS -m "The report $($lines.RPT) for site $($lines.SITE) has not been loaded by $($lines.EXPDTE)" -b "vp_reportnumber='$($lines.RPT)'; vp_sitename='$($lines.SITE)'; vp_expectedcomplete='$($lines.SIMEXPECTED)'; csg_environment='Production';"
"# # KEEP THIS TERMINATOR AT THE BEGINNING OF LINE
}
}
if ($statuscheck)
{
$output = Get-OLEDBData $connString $qry2
# $output | foreach {$_.completed}
write-host -nonewline $output.completed
}
So that you can see the data, below is a csv output from Oracle SQL Developer with the ACTUAL results to the query that is being referenced by my script. Of these results lines 4, 5, 6, 7, 8, 10 are the only ones being passed along in the ForEach loop, while the others are not even captured in the $output array. If anyone can advise of a method for getting all of the results passed along, I would appreciate it.
"SITE","RPT","LSDTE","EXPDTE","CIMEXPECTED","EXPECTED_FREQUENCY","DATE_TIMING","ETME"
"chrcse","CPHM-054","","2014/09/21 12:00:00","20140921120000","MONTHLY","1",
"chrcse","CPSM-226","","2014/09/21 12:00:00","20140921120000","MONTHLY","1",
"dsh","CPSD-176","2014/09/28 23:20:04","2014/09/30 04:00:00","20140930040000","DAILY","1",1.41637731481481481481481481481481481481
"dsh","CPSD-178","2014/09/28 23:20:11","2014/09/30 04:00:00","20140930040000","DAILY","1",1.4162962962962962962962962962962962963
"exp","CPSM-610","2014/08/22 06:42:10","2014/09/21 09:00:00","20140921090000","MONTHLY","1",39.10936342592592592592592592592592592593
"mdc","CPKD-264","2014/09/24 00:44:32","2014/09/30 04:00:00","20140930040000","DAILY","1",6.35771990740740740740740740740740740741
"nea","CPKD-264","2014/09/24 01:00:31","2014/09/30 03:00:00","20140930030000","DAILY","1",6.34662037037037037037037037037037037037
"twtla","CPOD-034","","2014/09/29 23:00:00","20140929230000","DAILY","0",
"twtla","CPPE-002","2014/09/29 02:40:35","2014/09/30 06:00:00","20140930060000","DAILY","1",1.27712962962962962962962962962962962963
"twtla","CPXX-004","","2014/09/29 23:00:00","20140929230000","DAILY","0",
It appears, actually, that somehow a comment that was in the query was causing this issue. I removed it and the results started returning normal. I have no idea why this would be the case, unless it has to do with the way the import works (does it import everything as one line?). Either way, the results are normal now.

PowerShell and SQL server

Currently I'm working on a PowerShell script which is extracting info about OS, hotfix and installed software.
Well, everything is working well if I want to export to txt file - everything.
But my new task is to upload this information to sql server.
So I'm creating a foreach loop to print installed software and put everything in server. PowerShell isn't showing any errors but I can't get this data into sql server.
$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Data Source=myserver; Initial Catalog=table; Integrated Security=SSPI;"
$conn.Open()
$soft = Get-WmiObject Win32_Product
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.Connection = $conn
$cmd = $conn.CreateCommand()
foreach ($Software in $soft){
$query = "INSERT INTO dbo.mytable (SoftName, SoftVersion) VALUES ('$($Software.Name)', $($Software.Version))" }
$cmd.CommandText = $query
$result = $cmd.ExecuteNonQuery
$conn.close()
So the idea is that when I run this script, I get all software installed on pc listed in sql server.
SoftName: SoftVersion:
Office 14.202
Sql 15
Thanks!
What do you get in $result variable when you run your script?
I believe, that you get signatures of overloaded methods with the name ExecuteNonQuery. The proper method call looks like this:
$result = $cmd.ExecuteNonQuery()
If you see errors I would recommend you to use code below to get full information about them:
$ErrorActionPreference = "Stop"
$Error.Clear()
try {
# Your code
}
catch {
$Error | Format-List * -Force
}
You are overwriting the insert command instead of appending into it. In addition, as levgen points out, you are missing method call syntax:
foreach ($Software in $soft){
$query = "INSERT INTO dbo.mytable (SoftName, SoftVersion) VALUES ('$($Software.Name)', $($Software.Version))"
} # Oops!
# Now $query contains only the last insert statement.
$cmd.CommandText = $query
$result = $cmd.ExecuteNonQuery # Should be .ExecuteNonQuery()
$conn.close()
Try something like so,
foreach ($Software in $soft){
$query += "INSERT INTO dbo.mytable (SoftName, SoftVersion) VALUES ('$($Software.Name)', $($Software.Version));"
} # Appending insert staements
# Now $query should contain lots of insert staements
$cmd.CommandText = $query
$result = $cmd.ExecuteNonQuery()
$conn.close()