Cannot update SQL Server table from powershell - powershell

I have created a test database in SQL Server 2016 Express, it holds 1 table labeled drivers.
I use PowerShell to perform ciminstance query of installed drivers, then insert those values into the test database driver table. (the insert works as expected)
The issue I have is attempting to update the driver table, only the last object is inserted into the database 40 times(that is how many drivers are returned from the ciminstance query). I have created 2 PowerShell scripts
Insert values
Update values
Stumped!
$database = 'test'
$server = 'groga\sqlExpress'
$table = 'dbo.Driver'
$SQLServer = "groga\sqlExpress"
$SQLDBName = "test"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database =
$SQLDBName; Integrated Security = True"
$SqlConnection.Open()
$today = Get-Date
$drivers = gcim win32_pnpsigneddriver -Property *
$model = gcim win32_computersystem -Property *
foreach($driver in $drivers)
{
if(!($driver.Description -match "Generic") -and $driver.Manufacturer -
notmatch 'Microsoft|Standard|Generic' -and $driver.DriverDate -ne $null)
{
$count = New-Object psobject -Property #{
'Date' = $driver.DriverDate
'Manufacturer' = $driver.Manufacturer
'Version' = $driver.DriverVersion
'PackageID' = "0"
'SKU' = $model.SystemSKUNumber
'Model' = $model.Model
'Today' = $today}
$col1 = $count.Date
$col2 = $count.Manufacturer
$col3 = $count.Version
$col4 = $count.PackageID
$col5 = $count.SKU
$col6 = $count.Model
$col7 = $count.Today
$update = #"
UPDATE $table
SET [Date]='$col1',
[Manufacturer]='$col2',
[Version]='$col3',
[PackageID]='$col4',
[SKU]='$col5',
[Model]='$col6',
[Today]='$col7'
"#
$dbwrite = $SqlConnection.CreateCommand()
$dbwrite.CommandText = $update
$dbwrite.ExecuteNonQuery()
}
}
$Sqlconnection.Close()

The UPDATE statement will apply to all rows that are matched by the query. So what your script is doing is setting ALL rows in the table to info for a driver then doing the same for the whole list.
You will need to determine the fields which uniquely identify each driver and then filter your query down to that. Looking at sample driver info, this could be Date, Manufacturer, Device Name (something you would need to add to your schema), DriverVersion.
Example with just Date, Manufacturer, DriverVersion:
$update = #"
UPDATE $table
SET [PackageID] = '$col4'
[SKU]='$col5',
[Model]='$col6',
[Today]='$col7'
WHERE [Date] = '$col1' AND [Manufacturer]='$col2' AND [Version]='$col3'
"#

Related

Determine the number of records in an OLEDB Recordset

I stole the following code that allows me to query a csv file with via a SQL query. The code reliably outputs a table which is nice. Ultimately what I would like to do is perform an action if the results of my query is zero records.
How would I go about doing that?
$firstRowColumnNames = "Yes"
$delimiter = ","
$provider = 'Microsoft.ACE.OLEDB.16.0'
$connstring = "Provider=$provider;Data Source=$(Split-Path $csv);Extended Properties='text;HDR=$firstRowColumnNames;';"
$tablename = (Split-Path $csv -leaf).Replace(".","#")
$sql = "SELECT * from [$tablename] Where sSamAccountName='acco'"
$sql
$conn = New-Object System.Data.OleDb.OleDbconnection
$conn.ConnectionString = $connstring
$conn.Open()
$cmd = New-Object System.Data.OleDB.OleDBCommand
$cmd.Connection = $conn
$cmd.CommandText = $sql
# Load into datatable
$dt = New-Object System.Data.DataTable
$dt.Load($cmd.ExecuteReader("CloseConnection"))
#Clean up
$cmd.dispose | Out-Null; $conn.dispose | Out-Null
#Output results
$dt | Format-Table -AutoSize
Following Dai's helpful comment, on PowerShell syntax you can check this using the -not logical operator as another alternative to Rows.Count -eq 0:
$dt = [System.Data.DataTable]::new()
$dt.Columns.AddRange(#('col1','col2'))
# DataTable only has 2 columns defined but now Rows
-not $dt.Rows.Count # -not 0 or -not $null -> $true // [int]$null = 0
[bool]$dt.Rows.Count # [bool]$null or [bool]0 -> $false
# Add a new Row to the DataTable
$row = $dt.NewRow()
$row['col1'] = 'ExampleVal1'
$row['col2'] = 'ExampleVal2'
$dt.Rows.Add($row)
-not $dt.Rows.Count # not 0 or not null when negated -> $false
[bool]$dt.Rows.Count # not 0 or not null in boolean expression -> $true
$dt.Dispose()

Data type mismatch when querying a CSV with ACE OLEDB provider

I am attempting to query a CSV file using the Microsoft ACE OLEDB provider. When I add "PrctBusy > 60" to the where clause I receive the Error "Data type mismatch in criteria expression." I have searched StackOverFlow and used google to search for solutions, I see this is not an uncommon issue. From my readings it looks to be datatype issue. The data in the column PrctBusy is all numeric. I think I need to force it to be number but I have not found a solution.
Below is the code I am currently working with:
$ArrayNameUtil = "000198701258"
$CatNameUtil = "FE_DIR"
$sdLocalPath = "D:\Logs\SANData\Perf"
$InputCSV = "VMaxSANReportUtilFile.csv"
$csv = Join-Path $sdLocalPath $InputCSV
$provider = (New-Object System.Data.OleDb.OleDbEnumerator).GetElements() | Where-Object { $_.SOURCES_NAME -like "Microsoft.ACE.OLEDB.*" }
if ($provider -is [system.array]) { $provider = $provider[0].SOURCES_NAME } else { $provider = $provider.SOURCES_NAME }
$connstring = "Provider=$provider;Data Source=$(Split-Path $csv);Extended Properties='text;HDR=$firstRowColumnNames;';"
$firstRowColumnNames = "Yes"
$delimiter = ","
$tablename = (Split-Path $csv -leaf).Replace(".","#")
$conn = New-Object System.Data.OleDb.OleDbconnection
$conn.ConnectionString = $connstring
$provider = (New-Object System.Data.OleDb.OleDbEnumerator).GetElements() | Where-Object { $_.SOURCES_NAME -like "Microsoft.ACE.OLEDB.*" }
if ($provider -is [system.array]) { $provider = $provider[0].SOURCES_NAME } else { $provider = $provider.SOURCES_NAME }
$connstring = "Provider=$provider;Data Source=$(Split-Path $csv);Extended Properties='text;HDR=$firstRowColumnNames;';"
$firstRowColumnNames = "Yes"
$delimiter = ","
$tablename = (Split-Path $csv -leaf).Replace(".","#")
$conn = New-Object System.Data.OleDb.OleDbconnection
$conn.ConnectionString = $connstring
$conn.Open()
#
$sql = "SELECT TimeStamp, count(PrctBusy) AS Above60 FROM [$tablename] WHERE array = '$ArrayNameUtil' and Category like '$CatNameUtil' and PrctBusy > 60 Group by TimeStamp "
$cmd = New-Object System.Data.OleDB.OleDBCommand
$cmd.Connection = $conn
$cmd.CommandText = $sql
$dtp = New-Object System.Data.DataTable
$dtp.Load($cmd.ExecuteReader())
Because of the pointer from TessellatingHeckler to Codeproject and some follow on queries, I was lead to http://aspdotnetcodes.com/Importing_CSV_Database_Schema.ini.aspx. I found that a schema.ini file in the same directory as the CSV file could specify the data type.
The schema.ini file ended up in the following format:
[VMaxSANReportUtilFile.csv]
ColNameHeader=True
Format=CSVDelimited
Col1=Array Text Width 20
Col2=TimeStamp Text Width 20
Col3=Category Text Width 20
Col4=Instance Text Width 20
Col5=PrctBusy Short
Col6=QueUtil Short
I went through several revisions to get the data type correct for an ACE OLE DB provider. If the columns are named the names need to be in the schema.ini file.

Comparing results from 2 database servers

My PowerShell script below works great and returns the two rows from different databases in 1 DataTable, but I can't work out how to compare these 2 rows.
The situation is I have 2 database servers, and want to compare max(id) from the same table on both, compare and then possibly alert if they're different.
The alerting bit I can do, but I haven't used DataTables before.
function readServer1 {
# Connection variables
$server = "db1"
$port = 1234
$driver = "Adaptive Server Enterprise"
$query = "select max(id) as 'id' from table"
$db = "db"
$uid = "uid"
$pwd = "pwd"
# Create Object and Connection
$conn = New-Object System.Data.Odbc.OdbcConnection
$conn.ConnectionString = "driver={$driver};db=$db;na=$server,$port;uid=$uid;pwd=$pwd;"
$conn.Open()
$cmd = New-Object System.Data.Odbc.OdbcCommand($query, $conn)
$cmd.CommandTimeout = 30
# Create a Data Table
$dt = New-Object System.Data.DataTable
$dt.Load($cmd.ExecuteReader())
$dt.Rows
# Close Connection
$conn.Close()
}
function readServer2 {
# Connection variables
$server = "db2"
$port = 1234
$driver = "Adaptive Server Enterprise"
$query = "select max(id) as 'id' from table"
$db = "db"
$uid = "uid"
$pwd = "pwd"
# Create Object and Connection
$conn = New-Object System.Data.Odbc.OdbcConnection
$conn.ConnectionString = "driver={$driver};db=$db;na=$server,$port;uid=$uid;pwd=$pwd;"
$conn.Open()
$cmd = New-Object System.Data.Odbc.OdbcCommand($query, $conn)
$cmd.CommandTimeout = 30
# Create a Data Table
$dt = New-Object System.Data.DataTable
$dt.Load($cmd.ExecuteReader())
$dt.Rows
# Close Connection
$conn.Close()
}
readServer1
readServer2
It returns results like this:
id
--
12345
12346
Just put function execution results to variables and compare them. Like so:
$var1 = readServer1 | Select -Expand id
$var2 = readServer2 | Select -Expand id
if ($var1 -ne $var2) { Do-That }

Trouble parsing SQL data with Powershell

I have, what I think, is a real head-scratcher.
I am accessing a database to get a list of accounts. Each account has an account_id and account_parent_id property (among several other properties). If the account is the child of another account, the account_parent_id has the account ID of the parent and if the account is a parent (or has no children), the account_parent_id is blank. There are only two levels, so if an account has one or more children, it will not have a parent.
I need the output to be the account number (if the account has no children) and the account number of the parent and all children (comma separated) if there are children. Here is the code I have:
$SQLServer = "<database fqdn>"
$SQLDBName = "<databaes name>"
$uid ="<username>"
$pwd = "<password>"
$SqlQuery = "SELECT * from <account table>"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; Persist Security Info = True; User ID = $uid; Password = $pwd;"
$SqlConnection.Open()
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$data = $DataSet[0].Tables
$SqlConnection.Close()
Foreach ($row in $data) {
Foreach ($account in $row) {
If ($account.parent_account_id -eq $row.account_id) {
$accts += $account.account_id
}
ElseIf ($account.parent_account_id -eq $account.account_id) {
$accts = $account.account_id
}
Return $accts
}
}
The problem is that I'm not getting anything at all into $accts. What am I missing here?
Thanks.
The best way to achieve your desired result is to do the mapping in SQL:
$SqlQuery = #'
SELECT t1.account_id AS parent, t2.account_id AS child
FROM <account table> t1 LEFT OUTER JOIN <account table> t2
ON t1.account_id = t2.parent_account_id
'#
Then you can extract the information you want via Group-Object and calculated properties:
$data | Group-Object parent |
Select-Object #{n='Parent';e={$_.Name}},
#{n='Children';e={$_.Group.child -join ','}}
If you're stuck with PowerShell v2 or earlier you need to replace $_.Group.child -join ',' with something like ($_.Group | Select-Object -Expand child) -join ','.
So, we did end up updating the SQL query. The data type I was getting back was a DataTable. I took out a row and used a loop like the following, to get he data I needed:
foreach ($item in $tableRow) {
$($item.account_ids)
}

Nesting dynamic parameters in PowerShell

I am working on a function that will insert a row into a SQL database. It is basically a simple change log to help me track what is changed on my various SQL instances. As part of this, I want to have the following parameters:
Timestamp
Server
Instance
Change
I've got the Timestamp, Change, and Server all figured out, but the Instance is giving me some trouble. The Server parameter is dynamic, as it pulls a list of SQL servers from my inventory. I then want the value of that parameter to be used in another dynamic parameter, which pulls a list of the instances that are on that server (also from my inventory). Here is what I have for the dynamic portion:
DynamicParam {
if (!(Get-Module sqlps)){ Pop-Location; Import-Module sqlps -DisableNameChecking; Push-Location }
$inventoryinstance = 'ServerName'
$newparams = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$server_query = 'SELECT [Name] FROM [ServerInventory].[dbo].[Servers] WHERE [TypeID] = 1 ORDER BY [Name]'
$servers = Invoke-Sqlcmd -serverinstance $inventoryinstance -query $server_query -connectiontimeout 5
# Populate array
$serverlist = #()
foreach ($servername in $servers.Name) {
$serverlist += $servername
}
$attributes = New-Object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Position = 1
$attributes.Mandatory = $true
$attributes.HelpMessage = "The server the change was made on"
# Server list parameter setup
if ($serverlist){ $servervalidationset = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $serverlist }
$serverattributes = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$serverattributes.Add($attributes)
if ($serverlist){ $serverattributes.Add($servervalidationset) }
$serverob = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Server", [String], $serverattributes)
$newparams.Add("Server", $serverob)
$instance_query = "SELECT [Name] FROM [ServerInventory].[dbo].[SQLInstances] WHERE [ServerID] = (SELECT [ServerID] FROM [ServerInventory].[dbo].[Servers] WHERE [Name] = '$($PSBoundParameters.Server)')"
$instances = Invoke-Sqlcmd -serverinstance $inventoryinstance -query $instance_query -connectiontimeout 5
# Populate array
$instancelist = #()
foreach ($instancename in $instances.Name) {
$instancelist += $instancename
}
$attributes = New-Object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Position = 2
$attributes.Mandatory = $false
$attributes.HelpMessage = "The instance the change was made on, do not specify for server-level changes"
# Server list parameter setup
if ($instancelist){ $instancevalidationset = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $instancelist }
$instanceattributes = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$instanceattributes.Add($attributes)
if ($instancelist){ $instanceattributes.Add($instancevalidationset) }
$instanceob = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Instance", [String], $instanceattributes)
$newparams.Add("Instance", $instanceob)
return $newparams
}
Everything seems to be working, except the value for the instance variable doesn't autocomplete. Is it possible to use the value of one dynamic parameter to generate another?