MSI SQL Query for column header called "Table" not working - sql

I'm using Powershell to query the _Validation table from an MSI Code as follows...
$Script:WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
$Script:MSIDatabase =
$WindowsInstaller.GetType().InvokeMember("OpenDatabase",
"InvokeMethod", $null, $WindowsInstaller, #($MSIPath, 0))
$Query = "SELECT Table, Column FROM _Validation"
$Script:View = $MSIDatabase.GetType().InvokeMember("OpenView",
"InvokeMethod", $null, $MSIDatabase, ($Query))
$Script:View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View,
$null)
$Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null,
$View, $null)
$ValidationTable =#()
While($Record -ne $null){
$Col1 = $Record.GetType().InvokeMember("StringData", "GetProperty",
$null, $Record, 1)
$Col2 = $Record.GetType().InvokeMember("StringData", "GetProperty",
$null, $Record, 2)
$ValidationTable += New-Object PsObject -Property #{Table = $Col1;
Column = $Col2}
$Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null,
$View, $null)
}
}
Now everything is working except for when I use "Table" in the query. From research I understand it may be an escape word in SQL so I've tried wrapping it in ` I've tired [ I've tried _Validation.Table and nothing works.
As I'm not good with SQL can anyone give me a hand?
Many thanks

Holy badger... the issue wasn't with the query it was with the fact i wrapped the query in double quotes not single quotes...
$Query = 'SELECT `Table`, Column FROM _Validation'
Works great.

Related

Convert date format to CSV export

I'm new to Poweshell, and I made a script that the result of an SQL Select is exported to csv. However I have two columns that are in the date format mm-dd-yyyy and I would like it to be in the format yyyy-MM-dd HH: mm: ss.fff. I was able to transform just one column, in the other I couldn't find a way to convert.
Could someone help me to convert the two columns?
#Connect to SQL and run QUERY
$SQLServer = "xxxx"
$SQLDBName = "xxxx"
$SQLUsername = "xxxx"
$SQLPassword = "nxxx"
$OuputFile = "c:\SQL_Export.csv"
$SqlQuery = "SELECT
rtrim(HANDLE) as Handle,
rtrim(EMPRESA) as Empresa,
rtrim(FILIAL) as Filial,
rtrim(OPERACAO) as Operacao,
rtrim(PESSOA) as Pessoa,
rtrim(dataemissao) as Dataemissao,
rtrim(documentodigitado) as Documentodigitado,
rtrim(dataultimaliq) as Dataultimaliq,
rtrim(ehprevisao) as Ehprevisao,
rtrim(status) as Status,
rtrim(entradasaida) as Entradasaida
FROM [$SQLDBName].[dbo].[FN_DOCUMENTOS]
where DATAULTIMALIQ >= '20200101'
ORDER BY handle ASC"
##Delete the output file if it already exists
If (Test-Path $OuputFile ){
Remove-Item $OuputFile
}
Write-Host "INFO: Exporting data from $SQLDBName to $OuputFile" -foregroundcolor white -backgroundcolor blue
## - Connect to SQL Server using non-SMO class 'System.Data':
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; User ID = $SQLUsername; Password = $SQLPassword"
$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)
$SqlConnection.Close()
#Output RESULTS to CSV
$DataSet.Tables[0] | select-Object -ExcludeProperty Dataemissao #{Name="Dataemissao";Expression={([datetime]$_.Dataemissao).ToString("yyyy-MM-dd HH:mm:ss.fff")}}, * | Export-Csv $OuputFile
(Get-Content $OuputFile) | Foreach-Object {$_ -replace '"', ""} | Set-Content $OuputFile -Encoding `UTF8`
the columns I need to convert are, "Dataemissao" and "Dataultimaliq"
In this case, you would need a very long line in the Select-Object:
$DataSet.Tables[0] |
Select-Object -ExcludeProperty Dataemissao, Dataultimaliq #{Name = "Dataemissao"; Expression= { '{0:yyyy-MM-dd HH:mm:ss.fff}'-f [datetime]$_.Dataemissao }},
#{Name = "Dataultimaliq"; Expression= { '{0:yyyy-MM-dd HH:mm:ss.fff}'-f [datetime]::ParseExact($_.Dataultimaliq, 'MMM dd yyyy hh:mm tt', [cultureinfo]"en-US") }},
* | Export-Csv -Path $OuputFile -Encoding UTF8 -NoTypeInformation
OR, you could add a small helper function above the code like
function Format-Date () {
param (
[string]$dateString,
[string]$format = 'MMM dd yyyy hh:mm tt'
)
$date = Get-Date
if ([datetime]::TryParseExact($dateString, $format, [cultureinfo]"en-US", 0, [ref]$date)) {
return '{0:yyyy-MM-dd HH:mm:ss.fff}'-f $date
}
}
and use that for the Dataultimaliq:
#{Name = "Dataultimaliq"; Expression= { Format-Date $_.Dataultimaliq }},
As you can see, I've put a second parameter $format in that defaults to ''MMM dd yyyy hh:mm tt', because that is the format the returned Dataultimaliq field is in.
You can also use that on the Dataemissao field, but I don't know what format that is in.. (apparently, your system can cast it to a DateTime object without any problems)
I see you read the csv file in later and remove the quotes from it. This can lead to various problems, because sometimes quotes are needed in a CSV file and you should not simply remove all of them. Have a look at this answer for code how you can do that safely
P.S. '{0:yyyy-MM-dd HH:mm:ss.fff}'-f [datetime]$_.Dataemissao is a more 'PowerShelly' way of getting a date in a specific format. It returns the same as ([datetime]$_.Dataemissao).ToString("yyyy-MM-dd HH:mm:ss.fff"), but is more concise.

Cyclic csv-Import to SQL file by file

I try to write a short script that cyclic imports csv-files to a SQL data table that are being dropped in an import folder ($sourceSQL). Each csv-file consists of one line of information (here three columns). To make sure that the file was successfully written to the data table, I check if the unique Id can be found in the table.
So far it works with the first file. However, the second file gets only moved to the destination folder without being written to the data table. I can't find the problem. Is it because of the $data variable?
$StartButton.Add_Click({
$script:ActiveLoop = $true
while ($script:ActiveLoop){
If (Test-Path $sourceSQL){
$data = $null
Do{
$data = import-csv $impcsv -Header A,B,C
foreach($i in $data)
{
$Id = $i.A
$State = $i.B
$Sequence = $i.C
$query = INSERT INTO $SQLTable (Id, State, Sequence)
VALUES ('$Id','$State','$Sequence')"
$impcsv = invoke-sqlcmd -Database $SQLDatabase -Query $query -serverinstance $SQLInstance -Username $SQLUsername -Password $SQLPassword}
$SqlQueryId = "SELECT TOP 1 Id from $SQLTable ORDER BY Id DESC"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLInstance; Database = $SQLDatabase; User ID = $SQLUsername; Password = $SQLPassword"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQueryId
$SqlCmd.Connection = $SqlConnection
$SqlConnection.Open()
$IdCheck= [string]$SqlCmd.ExecuteScalar()
$SqlConnection.Close()
} Until ($Id -eq $IdCheck)
Move-Item $sourceSQL -Destination $destinationSQL
}
[System.Windows.Forms.Application]::DoEvents()
}
})
$objForm.Controls.Add($StartButton)

PowerShell Script that Queries SQL Table to CSV File using Loop

I have a basic SQL table of Employees. Using a powershell script I want to export all the employees who have made over 1000 sales to a .csv file and the rest into a different .csv file. I want to accomplish this task by using a loop. I am new to powershell and want to learn the foundations. Can anyone help?
SQL Table (not real employees)
This is what I have so far:
$connection.Open()
[System.Data.SqlClient.SqlDataReader]$result = $cmd.ExecuteReader()
$highDestFile = "C:\high-sales.csv"
$lowDestFile = "C:\low-sales.csv"
while($result.Read()) {
$ename = $result.GetValue(3);
$job = $result.GetValue(4);
$sales = $result.GetValue(7);
$tableArray = New-Object System.Collections.ArrayList
$tableArray.Add($ename)
$tableArray.Add($job)
$tableArray.Add($sales)
if($sales -ge 1000) {
Out-File -FilePath $highDestFile -InputObject $tableArray -Encoding ASCII -Append
} else {
Out-File -FilePath $lowDestFile -InputObject $tableArray -Encoding ASCII -Append
}
}
$connection.Close()
I'm not real familiar with the method you're using to get your results, but I think I have something similar that might be easier to work with for you. It will get all results into PS, and you can filter things from there, rather than getting one result at a time. You obviously know how to make your own SqlConnection and SqlCommand, I'm just including them for future readers to reference.
# Define SQL query
$sqlQuery = #"
SELECT *
FROM MyTable
"#
# Create a SqlConnection to connect to the SQL DB
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = "Server = $SqlServer; Database =$SqlCatalog; User Id = $User; Password = $Password"
# Create a SqlCommand object to define the query
$sqlCmd = New-Object System.Data.SqlClient.SqlCommand
$sqlCmd.CommandText = $sqlQuery
$sqlCmd.Connection = $sqlConnection
# Create a SqlAdapter that actually does the work and manages everything
$sqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$sqlAdapter.SelectCommand = $sqlCmd
# Create an empty DataSet for the query to fill with its results
$dataSet = New-Object System.Data.DataSet
# Execute the query and fill the DataSet (then disconnect)
$sqlAdapter.Fill($dataSet)
$sqlConnection.Close()
# Convert DataSet table to array for ease of use
[Array]$Results = $dataSet.Tables[0]
Beyond that you could just use a pair of Where statements to filter your results, and output to files.
$Results | ?{[int]$_.Sales -ge 1000} | Set-Content $highDestFile
$Results | ?{[int]$_.Sales -lt 1000} | Set-Content $lowDestFile

What is returned from a SQL query into a PowerShell variable?

Here is the function I have setup that works just fine to send queries to a SQL database from PowerShell and return the results (the results are what I don't quite understand)
function Invoke-SQL
{
param (
[string]$server,
[string]$database,
[string]$Query
)
$connectionString = "Data Source=$server; " +
"Integrated Security=SSPI; " +
"Initial Catalog=$database"
$connection = new-object
system.data.SqlClient.SQLConnection($connectionString)
$command = new-object system.data.sqlclient.sqlcommand($Query, $connection)
$connection.Open()
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataSet) | Out-Null
$connection.Close()
$dataSet.Tables
}
If I run a query such as the one below (it returns no results, meaning there were no records that existed that matched the condition) why does it return nothing when I just put in $results? Why is the result 'Table' when I do Write-Host $results ? See below
PS>$results = Invoke-SQL -server 'servername' -database 'DBname' -Query "SELECT * FROM [DBname].[dbo].[TableName] WHERE UserID = 'x' AND ComputerName = 'x'"
PS>$results
PS>Write-Host $results
Table
When no records are found I thought it would be equal to "" or $null but it is not upon testing
$null test
PS>If ($results -eq $null) {
>> write-host "Null"}else{
>> write-host "Not Null"
>> }
Not Null
"" test
PS>If ($results -eq "") {
>> write-host "Empty"}else{
>> write-host "Not Empty"
>> }
If someone could explain this to me, and what options I might have in order to check if a query returns no results, that would be great!
Read the comments on the question post for more details.
In order to see if records were returned or not, this will return the number of rows (records) returned. Credit to #Bill_Stewart.
($results | Measure-Object).Count
#Tomalak provided a helpful link.
#BaconBits had this helpful tip to get the type of an object
$results.GetType().FullName
# or
$results | Get-Member
Thank you all for your help.

Insert Microsoft Updates into Database

I'm trying to modify this script so that it inserts the installed updates into an SQL Server database table.
$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Data Source=sqlserver; Initial Catalog=updates; Integrated Security=SSPI;"
$conn.Open()
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.Connection = $conn
$cmd = $conn.CreateCommand()
$wu = new-object -com “Microsoft.Update.Searcher”
$totalupdates = $wu.GetTotalHistoryCount()
$all = $wu.QueryHistory(0,$totalupdates)
$OutputCollection= #()
Foreach ($update in $all){
$Regex = “KB\d*”
$KB = $string | Select-String -Pattern $regex | Select-Object { $_.Matches }
$output = New-Object -TypeName PSobject
$output | add-member NoteProperty “HotFix ID” -value $KB.‘ $_.Matches ‘.Value
$output | add-member NoteProperty “Title” -value $string
$OutputCollection += $output
$cmd.CommandText += "INSERT INTO dbo.updates (hotfixid, hotfixdescription) VALUES ('$($kb.'$_.Matches'.Value)', ('$($string)'))"
}
$cmd.ExecuteNonQuery()
$conn.close()
At the moment, I'm getting correct number of rows for updates in sql server but it isn't showing hotfixid and in hotfix descriptien columns there is a only one update in all rows.
Thanks!
Do the INSERTs inside the loop. I would, however, recommend that you use prepared statements instead of building the SQL statements via string concatenation. Also, there's no need to build $OutputCollection objects when you're not using it anywhere.
Something like this should work:
...
$wu.QueryHistory(0, $totalupdates) | % {
$KB = $_.Title | ? { $_ -match '(KB\d+)' } | % { $matches[1] }
$cmd = $conn.CreateCommand()
$cmd.CommandText = "INSERT INTO dbo.updates (hotfixid, hotfixdescription) " +
"VALUES (#id, #descr)"
$cmd.Parameters.AddWithValue("#id", $KB)
$cmd.Parameters.AddWithValue("#descr", $_.Title)
$cmd.Prepare()
$cmd.ExecuteNonQuery()
}
...
Untested, though, since I don't have an SQL Server at hand. I also suspect that there's a more efficient way to handle the prepared statements, but I'm not that familiar with SQL Server.