Invoke-Sqlcmd Join variable as table - powershell

Is there a way to link a PS variable as a table within the Invoke-Sqlcmd?
I've tried LEFT JOIN $psvar as p on T1.ID=P.ID
I updated the script by making a DataTable from the advice of #Cpt.Whale.
# Define DataTable Columns
$tbl = New-Object system.Data.DataTable 'tbl_New'
$newcol = New-Object system.Data.DataColumn emplID,([string]); $tbl.columns.add($newcol)
$newcol = New-Object system.Data.DataColumn adGrp,([string]); $tbl.columns.add($newcol)
# Add data from Excel
$exelWkbk = '.\table.xlsx'
$excelQuery = '
SELECT F1 as emplID
,F2 as pcType
FROM [Sheet1$]
'
$queryOutput = Invoke-ExcelQuery $exelWkbk $excelQuery | Select-Object -Skip 1
$queryOutput | ForEach-Object {
$row = $tbl.NewRow()
$row.tmplID = ($_.emplID)
$row.adGrp = ($_.pcType)
$tbl.Rows.Add($row)
}
# Query SQL Data source joining Excel data
$sqlQuery = "
USE SQLDATABASE;
DECLARE #today as date = GETDATE();
SELECT emp.USER_ID
,wt.HOST
,a.pcType as DevicModel
FROM workstationTable as wt
JOIN employeeTable as emp on wt.USER_ID = emp.USER_ID
JOIN $tbl as a on emp.USER_ID = a.emplID
WHERE emp.NAME is not NULL
"
Invoke-Sqlcmd -Query $sqlQuery -ServerInstance 'dbName' |
Out-GridView

Powershell doesn't have a built-in function for doing SQL-style joins of different datasets. You can use Select-Object calculated properties for simple lookup-table type things, but I prefer using the Join-Object module built on LINQ:
# Gather both data sources into separate list variables:
$sqlData = Invoke-Sqlcmd -ServerInstance 'dbName' -Query '
USE SQLDATABASE;
DECLARE #today as date = GETDATE();
SELECT emp.USER_ID,wt.HOST
FROM workstationTable as wt
JOIN employeeTable as emp on wt.USER_ID = emp.USER_ID
WHERE emp.NAME is not NULL'
$excelData = Invoke-ExcelQuery $exelWkbk $excelQuery | Select-Object -Skip 1
# join the lists on a specific property
$joined = Join-Object -Type AllInLeft `
-Left $sqlData -LeftJoinProperty USER_ID `
-Right $excelData -RightJoinProperty EmplID
$joined | Out-GridView
Or since you're already using Excel, an SQL data connection in the file may be an option.

Related

How to get a nullable DateTime value?

My SQLite database contains a nullable DateTime field that I have to read in PowerShell. My select query:
$colId = "myPrimaryKeyColumn"
$col1 = "myColumnName"
$select = "SELECT CAST($col1 as nvarchar(20)) FROM $table ORDER BY $colId DESC LIMIT 1"
Which returns CAST(Alive as nvarchar(20)) : 04/11/2022 17:17:15. How to get just the 04/11/2022 17:17:15 part?
I tried :
Invoke-SqliteQuery -SQLiteConnection $con -Query $select | Where-Object { $_.$col1}
Invoke-SqliteQuery -SQLiteConnection $con -Query $select -As DataRow | Where-Object { $_.$col1}
Invoke-SqliteQuery -SQLiteConnection $con -Query $select -As PSObject| Where-Object { $_.$col1}
Which prints just an empty line.
You need to use -As SingleValue (you don't need Where-Object)
Invoke-SqliteQuery -SQLiteConnection $con -Query $select -As SingleValue

PowerShell - select number with decimal separator (DB2 SQL)

Cannot figure out how to contain decimal separator when selecting DB2 data with PowerShell.
DB2 table contains column with item price:
+------+---------+
| Item | Price |
+------+---------+
| A | 99,104 |
| B | 27,05 |
| C | 320,001 |
+------+---------+
This is part from Powershell script which gets this data:
$SQL = "SELECT Item, Price FROM Inventory"
$connection = New-Object System.Data.Odbc.OdbcConnection
$connection.ConnectionString = "DSN=$DNS;UID=$USERNAME;password=$PASSWORD"
$connection.open() | Get-Item -ErrorAction Stop
$cmd = New-object System.Data.Odbc.OdbcCommand($SQL,$connection)
$result = New-Object system.Data.DataSet
(New-Object system.Data.odbc.odbcDataAdapter($cmd)).fill($result) # here comma gets removed from Price
$connection.close()
$result.Tables[0] | Export-Csv -NoTypeInformation -Delimiter -Encoding UTF8 $OutputFile
This somehow selects data without decimals which is incorrect - prices are now enourmously high:
99104
2705
320001
I though that comma is removed during Export-Csv so added -UseCulture, but result is the same. It appears that comma is removed when data is selected:
New-Object system.Data.odbc.odbcDataAdapter($cmd)
My question is how can I fix this? Is there additional parameter or something is missing here?
I cant place a comment yet.
As others asked, what is the datatype for you price column at the database? Per your output it's left justified, so it does not seems to be a numeric type.
Making a simple test here with PRODUCT table from db2sample database:
And, also, you may try to use IBM.Data.Db2 .Net provider instead of using ODBC.
$dbFactory = [System.Data.Common.DbProviderFactories]::GetFactory('IBM.Data.DB2')
$connection = $dbFactory.CreateConnection()
$connection.ConnectionString = "Database=SAMPLE"
$connection.Open()
$da = $dbFactory.CreateDataAdapter()
$ds = new-object "System.Data.DataSet"
$cmd = $dbFactory.CreateCommand()
$cmd.Connection = $connection
$cmd.CommandText = "SELECT PID, PRICE FROM PRODUCT"
$da.SelectCommand = $cmd
$da.Fill($ds)
$ds.Tables[0]
Produces the expected decimal format.
PID PRICE
--- -----
100-100-01 9,99
100-101-01 19,99
100-103-01 49,99
100-201-01 3,99
$ds.Tables[0].Columns[1].DataType
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Decimal System.ValueType

Remove a Column from result of query user /server in Powershell

Currently I am using this to show the remote sessions on our servers, but I would like to edit the resulting table to remove the column SESSIONNAME.
param([string]$Server)
query user /server:$Server
I have tried:
a regular expression
format-table -property username,id,state,idletime,logontime
Select all the other columns.
All of these result in no output.
I tried the response from the post that has been linked here as a duplicate, but I believe it is not working because I have to fully qualify the server. I'm not sure how to tweak this code to show everything besides the sessionnames column.
param([string]$Input)
$Server = $Input + ".network"
$re = '(\w+)\s+?(\S*)\s+?(\d+)\s+Disc\s+(\S+)\s+(\d+\.\d+\.\d+)'
query user /server:$Server | Where-Object { $_ -match $re } | ForEach-Object
{
New-Object -Type PSCustomObject -Property #{
'Username' = $matches[1]
'SessionID' = $matches[3]
'IdleTime' = $matches[4]
'LogonTime' = $matches[5]
}
} | Select-Object Username, IdleTime

Inner Join in PowerShell (without SQL)

How do we make Inner-Join or something a Cross-Join in PowerShell or PowerCLI?
Even though im new to PowerCLI/PowerShell , I do have a basic grasp on them, yet have practically spent 2 days trying to figure this, going through numerous documentations and blogs to no avail.
All I really want to know is if after typing my command
Get-Content File.txt
and getting:
Output1 or Table1 is
Name: Abc
Group: Bad
Policy: Great
Name: redi
Group: Good
Policy: MAD
etc. etc.
100s of these, and obviously more than just the 3 elements of Name, Group, Policy each.
Table2/Output2
Name: Abc
Limit: 10
used: 5
Name: redi
Limit: 20
used: 1
etc. etc.
100s of these.
and like 13 more of these text file tables, all with the "Name" as unique.
How can I combine it into one output at the end using Name with all the other elements?
My most obvious thought was something akin to joins, even if I had to do them 1 at a time, but even that I cant figure out how to do.
Is there anyway to do this in PowerShell itself without me having to go into Python or SQL?
If yes is there a method that is able to combine fields in spots where it's null?
If its not clear what type of result I am hoping for it will look something akin to this:
Name: Abc
Group: Bad
Policy: Great
Limit: 10
used: 5
Name: redi
Group: Good
Policy: MAD
Limit: 20
used: 1
Paweł Dyl provided you a solution
based on your two tables. However you probably need a generic solution where you don't have to specify each property by name yourself.
I would combine each table to a an array. Group the tables on the Name property using the Group-Object cmdlet. Iterate over each group and create a PsObject using the properties:
$table1 = [PSCustomObject]#{ Name = 'Abc'; Group = 'Bad'; Policy = 'Great'}, [PSCustomObject]#{ Name = 'redi'; Group = 'Good'; Policy = 'MAD'}
$table2 = [PSCustomObject]#{ Name = 'Abc'; Limit = '10'; used = '5'}, [PSCustomObject]#{ Name = 'redi'; Limit = '20'; used = '1'}
$allTables = $table1 + $table2
$allTables | group Name | Foreach {
$properties = #{}
$_.Group | Foreach {
$_.PsObject.Properties | Where Name -ne 'Name' | Foreach {
$properties += #{
"$($_.Name)" = "$($_.Value)"
}
}
}
$properties += #{Name = $_.Name}
New-Object PSObject –Property $properties
}
Output:
Group : Bad
Policy : Great
Name : Abc
Limit : 10
used : 5
Group : Good
Policy : MAD
Name : redi
Limit : 20
used : 1
You can use simple loop join as follows:
$table1 = [pscustomobject]#{Name='Abc';Group='Bad';Policy='Great'},[pscustomobject]#{Name='redi';Group='Good ';Policy='MAD'}
$table2 = [pscustomobject]#{Name='Abc';Limit=10;used=5},[pscustomobject]#{Name='redi';Limit=20;used=1}
$table1 | % {
foreach ($t2 in $table2) {
if ($_.Name -eq $t2.Name) {
[pscustomobject]#{Name=$_.Name;Group=$_.Group;Policy=$_.Policy;Limit=$t2.Limit;Used=$t2.Used}
}
}
}
Assuming uniqueness of keys you can also use faster, hashtable approach:
$hashed = $table1 | group Name -AsHashTable
$table2 | % {
$matched = $hashed[$_.Name]
if ($matched) {
[pscustomobject]#{Name=$matched.Name;Group=$matched.Group;Policy=$matched.Policy;Limit=$_.Limit;Used=$_.Used}
}
}
You can also use generic solution and wrap it in function. It matches records by their property names:
function Join-Records($tab1, $tab2){
$prop1 = $tab1 | select -First 1 | % {$_.PSObject.Properties.Name} #properties from t1
$prop2 = $tab2 | select -First 1 | % {$_.PSObject.Properties.Name} #properties from t2
$join = $prop1 | ? {$prop2 -Contains $_}
$unique1 = $prop1 | ?{ $join -notcontains $_}
$unique2 = $prop2 | ?{ $join -notcontains $_}
if ($join) {
$tab1 | % {
$t1 = $_
$tab2 | % {
$t2 = $_
foreach ($prop in $join) {
if (!$t1.$prop.Equals($t2.$prop)) { return; }
}
$result = #{}
$join | % { $result.Add($_,$t1.$_) }
$unique1 | % { $result.Add($_,$t1.$_) }
$unique2 | % { $result.Add($_,$t2.$_) }
[PSCustomObject]$result
}
}
}
}
$table1 = [pscustomobject]#{Name='Abc';Group='Bad';Policy='Great'},
[pscustomobject]#{Name='redi';Group='Good ';Policy='MAD'},
[pscustomobject]#{Name='Not joined';Group='Very bad';Policy='Great'}
$table2 = [pscustomobject]#{Name='Abc';Limit=10;used=5},
[pscustomobject]#{Name='redi';Limit=20;used=1},
[pscustomobject]#{Name='redi';Limit=20;used=2}
#name is only common property, records joined by name
Join-Records $table1 $table2
#example2
$test1 = [pscustomobject]#{A=1;B=1;C='R1'},
[pscustomobject]#{A=1;B=2;C='R2'},
[pscustomobject]#{A=2;B=2;C='R3'}
$test2 = [pscustomobject]#{A=1;B=1;D='R4'},
[pscustomobject]#{A=3;B=2;D='R5'},
[pscustomobject]#{A=4;B=2;D='R6'}
Join-Records $test1 $test2 #joined by two common columns - A and B
You can also cascade calls:
$test1 = [pscustomobject]#{A=1;B=1;C='R1'},
[pscustomobject]#{A=1;B=2;C='R2'},
[pscustomobject]#{A=2;B=2;C='R3'}
$test2 = [pscustomobject]#{A=1;B=1;D='R4'},
[pscustomobject]#{A=3;B=2;D='R5'},
[pscustomobject]#{A=4;B=2;D='R6'}
$test3 = [pscustomobject]#{B=1;E='R7'},
[pscustomobject]#{B=2;E='R8'},
[pscustomobject]#{B=3;E='R9'}
#first join by common A and B, then join result by common B
Join-Records (Join-Records $test1 $test2) $test3
So I found an Answer which was more suitable and it uses the join-Object function which was defined below:
you can access it at https://github.com/RamblingCookieMonster/PowerShell/blob/master/Join-Object.ps1
All I really had to do was Define my outputs as $A and $B and $C and so on, and just
$Join1= Join-Object -Left $A -Right $B -LeftJoinProperty Name - RightJoinProperty Name
made $Join2 then 3 so on until I got it all done
$Join2 = Join-Object -Left $Join1 -Right $C -LeftJoinProperty Name -RightJoinProperty Name
$Join3 = Join-Object -Left $Join2 -Right $D -LeftJoinProperty Name -RightJoinProperty Name
$Join4 = Join-Object -Left $Join3 -Right $E -LeftJoinProperty Name -RightJoinProperty Name
Until I got it all done
$Table1 | Join $Table2 -Using Name
$Table1 | Join $Table2 #Cross Join
See: In Powershell, what's the best way to join two tables into one?

Formatting Powershell output from stored procedure

I'm running into a small issue trying to get the output from a stored procedure into a text file via. Powershell.
#Connection Object
$cn = New-Object System.Data.SqlClient.SqlConnection(
"Data Source=localhost; Database=test;User ID=test;Password=xyzzy;"
)
$q = "exec usp_Users"
#Data Adapter which will gather the data using our query
$da = New-Object System.Data.SqlClient.SqlDataAdapter($q, $cn)
#DataSet which will hold the data we have gathered
$ds = New-Object System.Data.DataSet
#Out-Null is used so the number of affected rows isn't printed
$da.Fill($ds) >$null| Out-Null
#Close the database connection
$cn.Close()
if($ds.Tables[0].Rows.Count -eq 0){
write-host '0:No Data found'
exit 2
}
$file = "C:\temp\" + "users" + $(Get-Date -Format 'MM_dd_yyyy') + ".txt"
$ds.Tables[0] | out-File $file -encoding ASCII -width 255
Here is the output:
Column1
-----
USER_NAME,USER_TYPE
test#spamex.com,MasterAdministrator
foo#hotmail.com,UserAdministrator
test4#test.com,Users
How can I get rid of the 'Column1' and the underline?
select-object with expanded property might help:
ds.Tables[0] | Select-Object -expand Column1 | out-file..
You can export a datatable directly into a csv file by using export-csv:
$ds.Tables[0] | export-csv tofile.csv -notypeinformation