Convert sql Collection to String in Powershell - powershell

I've been trying to pull data from a sql query and get it converted to HTML to finally embed the results in an email body.
My code is as follows;
$SQLCommand = New-Object System.Data.SqlClient.SqlCommand
$SQLCommand.CommandText = "SELECT DISTINCT SYS.Name,LDISK.DeviceID0,LDISK.Size0 AS DiskSizeMB,LDISK.FreeSpace0 AS FreeSpaceMB,SCCM.dbo.v_GS_WORKSTATION_STATUS.LastHWScan,SCCM.dbo.v_GS_LastSoftwareScan.LastScanDate
FROM v_FullCollectionMembership_Valid SYS
JOIN v_GS_LOGICAL_DISK LDISK ON SYS.ResourceID = LDISK.ResourceID
INNER JOIN SCCM.dbo.v_GS_WORKSTATION_STATUS
ON LDISK.ResourceID = SCCM.dbo.v_GS_WORKSTATION_STATUS.ResourceID
INNER JOIN SCCM.dbo.v_GS_LastSoftwareScan
ON SCCM.dbo.v_GS_LastSoftwareScan.ResourceID =
SCCM.dbo.v_GS_WORKSTATION_STATUS.ResourceID
WHERE
LDISK.DeviceID0 = 'C:' AND
LDISK.DriveType0 = 3 AND
((LDISK.FreeSpace0 <= ((LDISK.Size0 * 10) / 100)) OR
(LDISK.FreeSpace0 <= 1024)) AND
SYS.CollectionID = 'SMS00001'
ORDER BY
SYS.Name"
$SQLCommand.Connection = $SQLConnection
$SQLAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SQLCommand
$SQLDataset = New-Object System.Data.DataSet
$SqlAdapter.fill($SQLDataset) | out-null
$FileInfo = $SQLDataset.tables | FT -AutoSize
The resulting format of the data stored in $FileInfo looks good;
Name DeviceID0 DiskSizeMB FreeSpaceMB LastHWScan LastScanDate
---- --------- ---------- ----------- ---------- ------------
Server01 C: 53244 2010 7/28/2017 3:18:01 PM 7/28/2017 5:25:51 AM
...however when I pipe this to ConvertTo-HTML ($FileInfo | ConvertTo-HTML) the resulting format comes out like this;
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>HTML TABLE</title>
</head><body>
<table>
<colgroup><col/><col/><col/><col/><col/><col/></colgroup>
<tr><th>ClassId2e4f51ef21dd47e99d3c952918aff9cd</th><th>pageHeaderEntry</th><th>pageFooterEntry</th><th>autosizeInfo</th><th>shapeInfo</th><th>groupingEntry</th></tr>
<tr><td>033ecb2bc07a4d43b5ef94ed5a35d280</td><td></td><td></td><td>Microsoft.PowerShell.Commands.Internal.Format.AutosizeInfo</td><td>Microsoft.PowerShell.Commands.Internal.Format.TableHeaderInfo</td><td></td></tr>
<tr><td>9e210fe47d09416682b841769c78b8a3</td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td>27c87ef9bbda4f709f6b4002fa4af63c</td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td>27c87ef9bbda4f709f6b4002fa4af63c</td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td>27c87ef9bbda4f709f6b4002fa4af63c</td><td></td><td></td><td></td><td></td><td></td></tr>
When I look at the type of my $FileInfo I get this;
$FileInfo.GetType();
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
...so I suspect that the ConvertTo-HTML module is looking for input in string format however I can't seem to get this to work properly - even after trying options like $FileInfo | Out-String -Stream
I want to believe that this can be done easily - I just can't find the right approach. Thanks in advance!

Remove format-table, use select with exlude not necessary option with first table, try this
$SQLDataset.tables[0] |
select * -ExcludeProperty RowError, RowState, HasErrors, Name, Table, ItemArray |
ConvertTo-Html

Related

Handling SQLite Blob with Powershell

I am trying to copy Data from a SQLite DB via Powershell to another SQLite DB, but i have Problems handling a binary Blob from the Database.
The columns of the DB look like this:
ID (int), Alive (int), UID (string), Data (Blob)
In the DB, the Blob is Hexadecimal but when i have in Powershell it is decimal.
This is how i geht the Data:
$conSource = New-Object -TypeName System.Data.SQLite.SQLiteConnection
$conSource.ConnectionString = "Data Source=" + $db_source
$conSource.Open()
$sqlSource = $conSource.CreateCommand()
$sqlSource.CommandText = "SELECT * FROM Players WHERE UID = '" + $player_uid + "'"
$adapterSource = New-Object -TypeName System.Data.SQLite.SQLiteDataAdapter $sqlSource
$dataSource = New-Object System.Data.DataSet
[void]$adapterSource.Fill($dataSource)
$dataSource
$sqlSource.Dispose()
$conSource.Close()
I was able to convert it, but then it becomes a string and I have no idea how to get the hexadecimal string as a blob into the other database (via UPDATE).
Output of $dataSource.Tables.Data.GetType():
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array

Unable to export system.object in a readable form using PowerShell

I've looked through other answers and I am still struggling. I have some data that comes from a RESTAPI and I get that data using Invoke-RestMethod so it is already converted from the returned JSON
$scans = Invoke-RestMethod -URI "https://<url>/api/v1/<api key>/scans"
I get back an object with two fields
Message:
Data:
$Scans.Data contains a hash table converted from the JSON output with each entry in the table having the following key:value pairs
ScanID
Username
Targets
Name
This is the output of $scans | Get-Member
$scans | gm
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
name NoteProperty string name=<redacted>
scan_id NoteProperty string scan_id=<redacted>
targets NoteProperty Object[] targets=System.Object[]
user_name NoteProperty string user_name=<redacted>
I need to export all data into a CSV, but $scan.data.targets only shows as System.Object[]
I can't use -expandproperty because I am selecting multiple fields.
How do I convert $scans.data.targets into a readable form for the export, whilst keeping it linked with the other fields for that entry?
something equivalent to:
$scans.data | export-CSV <filepath>
but exporting in a format that is readable when I open the CSV
As commented, you could opt for a combined field of targets in the output:
# demo
$scan = [PsCustomObject]#{data = [PsCustomObject]#{name = 'blah'; scan_id = 123; targets = 'abc','def','ghi'; user_name = 'ItIsMe'}}
$scan.data | Select-Object *, #{Name = 'targets'; Expression = {$_.targets -join '; '}} -ExcludeProperty targets |
Export-Csv -Path 'D:\Test\blah.csv' -NoTypeInformation
which (when displayed in the console) looks like this:
name scan_id user_name targets
---- ------- --------- -------
blah 123 ItIsMe abc; def; ghi
Or create output where every target gets its own row in the CSV:
$result = foreach ($item in $scan.data) {
foreach ($target in $item.targets) {
[PsCustomObject]#{
Name = $item.name
ScanID = $item.scan_id
Target = $target
UserName = $item.user_name
}
}
}
# output on screen
$result
# output to CSV file
$result | Export-Csv -Path 'D:\Test\blah.csv' -NoTypeInformation
which gives you this:
Name ScanID Target UserName
---- ------ ------ --------
blah 123 abc ItIsMe
blah 123 def ItIsMe
blah 123 ghi ItIsMe

Weird Object[] casting from PSObject through a function call

I'm currently coding a function able to cast an ADO (EML attachments from Email) into a PSObject. A stub of the code looks like this:
function Get-OriginalMailAttributes([__ComObject]$email){
#.DESCRIPTION Downloads in Temp the attachment and open it.
# Returns PSObjectwith values.
#STEP 1: Download EML
$TEMPFILE = "..."
if($email.Attachments.Count){
$attachment=$email.Attachments|?{$_.Filename.endsWith(".eml")} | select -First 1
$fileName = $TEMPFILE + $(New-Guid | select -exp Guid) + ".eml"
$attachment.SaveAsFile($fileName)
#STEP2 : Retrieve EML Objects
$adoDbStream = New-Object -ComObject ADODB.Stream
$adoDbStream.Open()
$adoDbStream.LoadFromFile($fileName)
$cdoMessage = New-Object -ComObject CDO.Message
$cdoMessage.DataSource.OpenObject($adoDbStream, "_Stream")
#STEP 3: Bind values
$attributes = New-Object PSObject -Property #{
From = [string]($cdoMessage.Fields.Item("urn:schemas:mailheader:from").Value.toString())
}
#STEP 4: Cleanup
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($cdoMessage)
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($adoDbStream)
Remove-Item $fileName
return $attributes
# Note that the debugger acknowledge the fact that $attributes is PSObject.
}
}
I'm then calling the function at some point:
$sender_raw = Get-OriginalMailAttributes $email
$sender_raw.getType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
$sender_raw
0
0
From
----
"Bob" <dummy#contoso.com>
$sender_raw.From # Crashes.
Where does this behavior come from?
Typically when you have "extra" values in the output from a function it's because you're calling methods that have return values and not dealing with those values.
.Add() methods are notorious for outputting the index of the item that was added.
To avoid this problem, you have a few options:
Cast to void: [void]$collection.Add($item)
Assign to $null: $null=$collection.Add($item)
Pipe to out-null: $collection.Add($item) | out-null
I don't see any obvious methods in your code, but adding Write-output 'Before Step 1' etc. throughout the code should make it clear where the offending statements are.
This weird behavior comes from multi-returns from PowerShell functions.
PS> function f() {
1+1
return 4
}
PS> f
2
4
In short, if one-liners are returning values, they are added as an Object[] array to the output.
While this is a particularly chaotic way to handle return values of a function, my solution on my code will be to redirect output NULL:
stub...
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($cdoMessage) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($adoDbStream) | Out-Null
See Suppressing return values in PowerShell functions

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

Unable to set Comobject Value

I have a snippet of code that opens a word template, then attempts to set values of named FormFields.
$options = #{
'foo' ='bar';
'bizz' = 'buzz';
}
$document = 'C:\Form_template.doc'
$word = new-object -ComObject Word.application
$doc = $word.Documents.Open($document)
$word.visible = $true
$fields = $doc.FormFields
$fields.item('foo').Result = $options['foo']
$fields.item('bizz').Result = $options['bizz']
When running this snippet, the form fields are not set properly. However, when I run
$fields.item('foo').Result = 'bar'
$fields.item('bizz').Result = 'buzz'
The values are set as desired.
Edit: Here's an example in Interactive shell
PS C:\>$fields.item('foo').Result = $options['foo']
PS C:\>$fields.item('bizz').Result = $options['bizz']
PS C:\> $doc.FormFields.Item('foo').Result
PS C:\> $doc.FormFields.Item('bizz').Result
PS C:\>#Now let's try setting the values directly with a string.
PS C:\>$fields.item('foo').Result = 'bar'
PS C:\>$fields.item('bizz').Result = 'buzz'
PS C:\> $doc.FormFields.Item('foo').Result
bar
PS C:\> $doc.FormFields.Item('bizz').Result
buzz
Why am I not able to set the FormField values using values from the hash?
Per a suggestion from Ben casting the string with [string]$options['bizz'] resulted in setting the value correctly.
PS C:\>$fields.item('bizz').Result = [string]$options['bizz']
PS C:\> $doc.FormFields.Item('foo').Result
buzz
Upon further investigation I found that casting the hash value to string returned a different type vs using .toString()
PS C:\> $options['bizz'].getType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
PS C:\> $options['bizz'].toString().getType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
PS C:\> [string]$options['bizz'].getType()
string
I'm interested in why that is, but that would be a topic for another thread. Proper casting resolved my issue.