Taking a string and formatting into table - string

I have the output of an Invoke-SSHCommand command for a host that gives me the following info in the form of a string:
PS C:\Users\user> $t.Output
system> Machine Type-Model Serial Number UUID
-------------- --------- ----
8871AC1 XXXXXXX ABCDEFGHIJKLMNOPQRSTUV
system>
Does anybody have any advice or hints as to how to take the above output and manipulate it to a CSV or something that would give me the functionality of the following:
PS user> $t.Output.UUID
ABCDEFGHIJKLMNOPQRSTUV
PS user> $t.Output.Serial
XXXXXXXX
At the moment, the most I can do with it is below:
PS C:\Users\user> $t.Output.Get(0)
system> Machine Type-Model Serial Number UUID
Any help is appreciated.

Assuming $t.Output is not an object, but rather an unstructured text, hence you can't use ConvertTo-Csv, you first have to create a structured object from your unstructured text. This is what ConvertFrom-String is for.
Example
$tObject = $t.Output | ConvertFrom-String -PropertyNames 'Machine Type-Model', 'Serial Number', 'UUID' | Select-Object -Skip 2
$tObject | ConvertTo-Csv -NoTypeInformation
"Machine Type-Model","Serial Number","UUID"
"8871AC1","XXXXXXX","ABCDEFGHIJKLMNOPQRSTUV"

Related

How to import an array of PSObjects from a text file?

This is simple enough:
PS C:\Users\saunders\Desktop\data>
PS C:\Users\saunders\Desktop\data> ls .\test.csv
Directory: C:\Users\saunders\Desktop\data
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2023-01-31 1:38 PM 640 test.csv
PS C:\Users\saunders\Desktop\data>
PS C:\Users\saunders\Desktop\data> cat .\test.csv
UserPrincipalName,"DisplayName","Title","UserType","IsLicensed"
LeeG#lazydev.com,"Lee Gu","jr engineer","Member","True"
MeganB#lazydev.com,"Megan Bowen","recruiter","Member","True"
GradyA#lazydev.com,"Grady Archie","sr engineer","Member","True"
MiriamG#lazydev.com,"Miriam Graham","Director","Member","True"
openmailbox#lazydev.com,"openmailbox",,"Member","False"
JohannaL#lazydev.com,"Johanna Lorenz","Senior Engineer","Member","True"
JoniS#lazydev.com,"Joni Sherman","recruiter","Member","False"
AlexW#lazydev.com,"Alex Wilber","Marketing Assistant","Member","True"
IsaiahL#lazydev.com,"Isaiah Langer","Sales Rep","Member","True"
PS C:\Users\saunders\Desktop\data>
PS C:\Users\saunders\Desktop\data> $test = Import-CSV .\test.csv
PS C:\Users\saunders\Desktop\data>
PS C:\Users\saunders\Desktop\data> $test[3]
UserPrincipalName : MiriamG#lazydev.com
DisplayName : Miriam Graham
Title : Director
UserType : Member
IsLicensed : True
PS C:\Users\saunders\Desktop\data>
But how would CSV formatted data be obtained from a formatted text file?
PS C:\Users\saunders\Desktop\data>
PS C:\Users\saunders\Desktop\data> $records = Get-Content .\records.txt
PS C:\Users\saunders\Desktop\data>
PS C:\Users\saunders\Desktop\data> $records
UserPrincipalName : LeeG#lazydev.com
DisplayName : Lee Gu
Title : jr engineer
UserType : Member
IsLicensed : True
UserPrincipalName : MeganB#lazydev.com
DisplayName : Megan Bowen
Title : recruiter
UserType : Member
IsLicensed : True
UserPrincipalName : GradyA#lazydev.com
DisplayName : Grady Archie
Title : sr engineer
UserType : Member
IsLicensed : True
UserPrincipalName : MiriamG#lazydev.com
DisplayName : Miriam Graham
Title : Director
UserType : Member
IsLicensed : True
UserPrincipalName : openmailbox#lazydev.com
DisplayName : openmailbox
Title :
UserType : Member
IsLicensed : False
UserPrincipalName : JohannaL#lazydev.com
DisplayName : Johanna Lorenz
Title : Senior Engineer
UserType : Member
IsLicensed : True
UserPrincipalName : JoniS#lazydev.com
DisplayName : Joni Sherman
Title : recruiter
UserType : Member
IsLicensed : False
UserPrincipalName : AlexW#lazydev.com
DisplayName : Alex Wilber
Title : Marketing Assistant
UserType : Member
IsLicensed : True
UserPrincipalName : IsaiahL#lazydev.com
DisplayName : Isaiah Langer
Title : Sales Rep
UserType : Member
IsLicensed : True
PS C:\Users\saunders\Desktop\data>
So that the data for each record is transposed and then written to a row in a CSV file. No doubt there's a term for this inverse operation. It doesn't have to be CSV per se, it's just that the above sample originates as CSV.
Please do correct any terminological errors.
as it stands, no the $records object cannot itself be directly exported back to CSV with:
PS C:\Users\saunders\Desktop\data>
PS C:\Users\saunders\Desktop\data> $records = Get-Content .\records.txt
PS C:\Users\saunders\Desktop\data>
PS C:\Users\saunders\Desktop\data> Export-Csv $records
Export-Csv : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Path'. Specified method is not supported.
At line:1 char:12
+ Export-Csv $records
+ ~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Export-Csv], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.ExportCsvCommand
PS C:\Users\saunders\Desktop\data>
as it will first have to parsed somehow.
The text format your showing is a for-display format, as produced by PowerShell's Format-List cmdlet.
As such, it isn't meant for programmatic processing, so there's no standard cmdlet that can parse that format.
For programmatic processing, a structured text format should be used, such as JSON or CSV, or - for the most structural flexibility and best (albeit limited) type fidelity - PowerShell's XML-based CLIXML format, as produced by Export-Clixml and understood by Import-Clixml.
If you're stuck with the given format, you'll have to do your own parsing; e.g.:
# Outputs [pscustomobject] instances that can be exported to CSV, for instance.
((Get-Content -Raw records.txt) -replace ':', '=') -split '\r?\n\r?\n' |
ConvertFrom-StringData |
ForEach-Object { [pscustomobject] $_ }
The above is relatively simple, but:
assumes that none of the property values contain : (this could be worked around) and that all values are single-line; also, the values are subject to interpretation of \ chars. as the start of escape sequence (see cmdlet link in next bullet point).
has one unavoidable drawback: because ConvertFrom-StringData outputs inherently unordered hashtables, the property order isn't preserved.
Potentially fixing this problem in a future PowerShell version (post-7.3.2, current as of this writing) is the subject of GitHub issue #19070
If maintaining the property order is important, and you know the property names, you can pipe to a Select-Object call with the property names listed in the desired order as its -Property argument.
Otherwise, you'll have to parse the text-file lines yourself.
Note: I suggest avoiding the obsolescent, Windows-only ConvertFrom-String cmdlet, despite its allure:
It provides separator-based parsing as well as heuristics-based parsing based on templates containing example values.
The separator-based parsing applies automatic type conversions that you cannot control, and the template language is poorly documented, with the exact behavior being hard to predict (as is unavoidable in a heuristics-based solution).
With Windows PowerShell specifically, you have the option of using ConvertFrom-String's template-based parsing capability:
# define a template using the data from 2 consecutive records from the input data
$recordTemplate = #'
UserPrincipalName : {UserPrincipalName*:LeeG#lazydev.com}
DisplayName : {DisplayName:Lee Gu}
Title : {Title:jr engineer}
UserType : {UserType:Member}
IsLicensed : {IsLicensed:True}
UserPrincipalName : {UserPrincipalName*:MeganB#lazydev.com}
'#
# ConvertFrom-String will use the example(s) in the template to infer the format of the remaining records
Get-Content .\records.txt | ConvertFrom-String -TemplateContent $recordTemplate
While not fully realized, something like
wsl >
wsl > pwd
/mnt/c/WINDOWS/system32/awk
wsl >
wsl > awk '{print $1}' input.txt | datamash transpose
A B C D
wsl >
wsl > awk '{print $2}' input.txt | datamash transpose
2014 2013 2014 2014
wsl >
wsl > awk '{print $3}' input.txt | datamash transpose
1002 990 2030 599
wsl >
wsl > cat input.txt
A 2014 1002
B 2013 990
C 2014 2030
D 2014 599
wsl >
where the matrix is transposed one line at a time per record.
The output above, when concatted, would be:
A B C D
2014 2013 2014 2014
1002 990 2030 599
Which, at first glance at least, would be easily imported as CSV. Not a PowerShell solution, however.

Send the output file to a mail without changing the data

I am having a output file called result from a script as below:
|first_t | PASS |
|second_t | PASS |
|third_t | File_Error |
And I used the below column command to get the output in sequence as below:
column -t -s " " result > result_new
|first_t | PASS |
|second_t | PASS |
|third_t | File_Error |
And after sending the result_new file to the mail using below mail command as in the below script I am not getting the expected output.
#!/bin/bash
mail -s "COLUMN CHECK_1" name#gmail.com < result_new
I am getting this output in my mail:
|first_t | PASS |
|second_t | PASS |
|third_t | File_Error |
My expected output in mail should be this:
|first_t | PASS |
|second_t | PASS |
|third_t | File_Error |
Can someone tell me what should I do here?
I'm assuming the issue is with the (receiving) email client and that it's displaying the message using a variable width font (eg, spaces aren't as wide as letters); to get around this issue I can think of a couple options:
send the output as an ascii text file attachment (user would need to open the attachment with an ascii text reader - eg, vim, notepad, etc)
format the outgoing email (eg, MIME + 'text/html') so the (receiving) email client knows to display the content as fixed-width text (though at this point you now need to look at your options on the sending end as to how to send such a formatted email - eg, sendmail, mailx, mutt, whatever mail points to on your system, etc; this also assumes the (receiving) email client is capable of displaying MIME encoded messages)
One simple sendmail example I've used when sending emails (from linux to Windows/Mac users) where I want the contents of the email to be displayed as fixed-width text:
$ subject="this is the subject of my email"
$ emlist="list_of_email_addresses"
$ mailfile=$(mktemp)
$ echo "Subject: ${subject}
MIME-Version: 1.0
Content-Type: text/html
<html><body><pre>" > "${mailfile}"
cat result_new >> "${mailfile}"
echo "</pre></body></html>" >> "${mailfile}"
$ cat ${mailfile} | /usr/sbin/sendmail ${emlist}
$ 'rm' -rf "${mailfile}"
If you don't have access to sendmail then you may need to do some testing with your mail (sending) program to see how to send a MIME encoded message ...

Select-Object Expression as a string

If I type out my Powershell select-object expression like below:
$csvdata | Select-Object #{expression={$_.1}; label='first'}
I receive desired output:
first
-
mike
john
But if I store the expression as a string first and then call that string as the expression to select-object:
$tstexp = "#{expression={`$_.1}; label='first'}"
$csvdata | Select-Object $tstexp
The output doesn't evaluate correctly and is used instead as the object name.
#{expression={$_.1}; label='first'}
-------------------------------
Is it possible to pass select-object an expression list as a string?
You can pass it as a [Hashtable]:
$tstexp = #{expression={$_.1}; label='first'}
$csvdata | Select-Object $tstexp
(just remove the quotes).
If it must be a string (I can only imagine that you are reading/generating it from outside your script), then you could evaluate it as a script block:
# Create the string
$tstexp = "#{expression={$_.1}; label='first'}"
# Convert to script block
$tstblock = [scriptblock]::Create($tstexp)
# Execute script block
$tstval = & $tstblock
# $tstval now contains the hashtable
$csvdata | Select-Object $tstval
Edit
If you must use a string, it's easier to use Invoke-Expression as Jeroen Mostert's answer explains (but of course, avoid the string if possible).
You're looking for Invoke-Expression:
$csvdata | select-object (Invoke-Expression $tstexp)

using Powershell, how can I find a specific line in a multi-line variable, then locate the 3rd word or value within the line

I'm using Windows 2012 powershell to scrape values from a 3rd party executable. My goal is to re-arrange and simplify the output of the tool to display only a subset of the content and allow me to collect the data on many devices at once. I have most of the script working, but I'm stuck on a simple task that's so easy in Linux bash.
The 3rd party program is pulling status of a computer device to standard out. I can successfully set the standard out content to a variable. For example:
PS C:\> $status = mycmd.exe device1 --status
PS C:\> $status
The $status variable would return a multi-line list of values as follows:
Device Number: 1
PCIe slot: 3
Firmware Version: 5.1.4
Temperature: 45C
State: Online
In this case, I would like to create a new variable for the firmware version. In Linux I would use something like this (although there are many options available):
Firmware=$(mycmd device1 --status | grep "Firmware" | cut -c 19-24)
In Powershell I can use the Select-String command to can find the "firmware" pattern and set it to a varible as follows:
$Firmware = $Status | select-string -Pattern "Firmware Version"
This gives me the entire Firmware version line. I can't figure out how to substring just the version number from the line as Powershell seems to only want to manipulate the item I'm searching for and not the content next to the pattern. Since this isn't a built in command with an object name, manipulating text seems much more difficult.
I would like the $Firmware variable to equal "5.1.4" or it could be placed into another variable if necessary.
Firmware = ($Status | Select-String -Pattern '\d{1}\.\d{1,2}\.\d{1,2}' -AllMatches | % { $_.Matches } | % { $_.Value }
$Firmware = ($Status | Select-String -Pattern '(?<=Firmware Version:\s+)[\d.]+').Matches.Value
The regular expression here is looking for 1 or more combinations of digits \d and literal dots which are preceded by the Firmware Version: line.
Note that Select-String returns an object, so we use .Matches.Value to get the actual match (which in this case will only be the number).
Using -replace with a multi-line regex:
$Var =
#'
Device Number: 1
PCIe slot: 3
Firmware Version: 5.1.4
Temperature: 45C
State: Online
'#
$Firmware = $var -replace '(?ms).+^Firmware Version:\s+([0-9.]+).+','$1'
$Firmware
5.1.4
This works...I set used a [system.version] to parse the version number (and remove spaces).
$FirmwareLine = $Status | select-string -Pattern "Firmware Version"| select -expand line
[system.version]$firmware=$firmwareLine -split ":" | select -first 1 -skip 1
If you just need the string, you can remove the "cast"
$firmware=$firmwareLine -split ":" | select -first 1 -skip 1
Another option is to replace the colons with '=', converting the output to key=value pairs and then turn that into a hash table (and then to a PS Object if you want) using ConvertFrom-StringData:
$Var =
#'
Device Number: 1
PCIe slot: 3
Firmware Version: 5.1.4
Temperature: 45C
State: Online
'#
$DeviceStatus = New-object PSObject -Property $(ConvertFrom-StringData $var.Replace(':','='))
$DeviceStatus.'Firmware Version'
5.1.4

How to reuse Cucumber step definition with a table for the last parameter?

This code:
Then %{I should see the following data in the "Feeds" data grid:
| Name |
| #{name} |}
And this one:
Then "I should see the following data in the \"Feeds\" data grid:
| Name |
| #{name} |"
And this:
Then "I should see the following data in the \"Feeds\" data grid:\n| Name |\n| #{name} |"
And even this:
Then <<EOS
I should see the following data in the "Feeds" data grid:
| Name |
| #{name} |
EOS
Gives me:
Your block takes 2 arguments, but the Regexp matched 1 argument.
(Cucumber::ArityMismatchError)
tests/endtoend/step_definitions/instruments_editor_steps.rb:29:in `/^the editor shows "([^"]*)" in the feeds list$/'
melomel-0.6.0/lib/melomel/cucumber/data_grid_steps.rb:59:in `/^I should see the following data in the "([^"]*)" data grid:$/'
tests/endtoend/instruments_editor.feature:11:in `And the editor shows "myFeed" in the feeds list
This one:
Then "I should see the following data in the \"Feeds\" data grid: | Name || #{name} |"
And this one:
Then "I should see the following data in the \"Feeds\" data grid:| Name || #{name} |"
Gives:
Undefined step: "I should see the following data in the "Feeds" data grid:| Name || myFeed |" (Cucumber::Undefined)
./tests/endtoend/step_definitions/instruments_editor_steps.rb:31:in `/^the editor shows "([^"]*)" in the feeds list$/'
tests/endtoend/instruments_editor.feature:11:in `And the editor shows "myFeed" in the feeds list'
I've found the answer myself:
steps %Q{
Then I should see the following data in the "Feeds" data grid:
| Name |
| #{name} |
}
NOTE ON THE ABOVE: might seem obvious, but the new line after the first '{' is soooooo important
Another way:
Given /^My basic step:$/ do |table|
#do table operation
end
Given /^My referring step:$/ do |table|
table.hashes.each do |row|
row_as_table = %{
|prop1|prop2|
|#{row[:prop1]}|#{row[:prop2]}|
}
Given %{My basic step:}, Cucumber::Ast::Table.parse(row_as_table, "", 0)
end
end
You can also write it this way, using #table
Then /^some other step$/ do
Then %{I should see the following data in the "Feeds" data grid:}, table(%{
| Name |
| #{name} |
})
end
Consider using
Given /^events with:-$/ do |table|
Given %{I am on the event admin page}
table.hashes.each do |row|
Given %{an event with:-}, Cucumber::Ast::Table.new([row]).transpose
end
end
I find that much more elegant that building up the table by hand.
events with:- gets a table like this
| Form | Element | Label |
| foo | bar | baz |
and an event with:- gets a table like
| Form | foo |
| Element | bar |
| Label | baz |

Resources