PowerShell format text file - powershell

I get the below output from a PowerShell query. I don't have access to the server to run the query, so I have no option to influence the output format.
Example:
Name : folderC
FullName : D:\folderA\folderB\folderC
Length :
CreationTime : 2/8/2014 11:12:58 AM
LastAccessTime: 2/8/2014 11:12:58 AM
Name : filename.txt
FullName : D:\folderA\folderB\filename.txt
Length : 71560192
CreationTime : 11/25/2015 3:10:43 PM
LastAccessTime: 11/25/2015 3:10:43 PM
How can I format above content to get something more usable, maybe like a table format like so:
Name|FullName|Length|CreationTime|LastAccessTime

I think you need to split the text into records, replace the colons with equals so that you can use the ConvertFrom-StringData to turn each record into a hash which you can then feed into New-Object to convert into an object. Outputting the the object into pipe separated data can then be done with the ConvertTo-Csv. Something like so:
$x = #"
Name : folderC
FullName : D:\folderA\folderB\folderC
Length : 0
CreationTime : 2/8/2014 11:12:58 AM
LastAccessTime : 2/8/2014 11:12:58 AM
Name : filename.txt
FullName : D:\folderA\folderB\filename.txt
Length : 71560192
CreationTime : 11/25/2015 3:10:43 PM
LastAccessTime : 11/25/2015 3:10:43 PM
"#
($x -split '[\r\n]+(?=Name)') | % {
$_ -replace '\s+:\s+', '='
} | % {
$_ | ConvertFrom-StringData
} | % {
New-Object psobject -Property $_
} | ConvertTo-Csv -Delimiter '|' -NoTypeInformation

As #alroc notes in a comment on the question, it is possible that objects are available to the OP, given that they state that the output is "from a Powershell query" - if so, simple reformatting of the object array using the usual cmdlets is an option.
By contrast, this answer assumes that only a text representation, as printed in the question, is available.
Dave Sexton's answer is a simpler and more elegant choice, if:
the input has no empty values (the OP's sample input does).
the input file is small enough to be read into memory as a whole.
Consider the approach below to avoid the issues above and/or if you want more control over how the input is converted into custom objects, notably with respect to creating properties with types other than [string]: extend the toObj() function below (as written, all properties are also just strings).
Get-Content File | % `
-begin {
function toObj([string[]] $lines) {
$keysAndValues = $lines -split '(?<=^[^ :]+)\s*: '
$htProps = #{}
for ($i = 0; $i -lt $keysAndValues.Count; $i += 2) {
$htProps.($keysAndValues[$i]) = $keysAndValues[$i+1]
}
return [PSCustomObject] $htProps
}
$lines = #()
} `
-process {
if ($_.trim() -ne '') {
$lines += $_
} else {
if ($lines) { toObj $lines }
$lines = #()
}
} `
-end {
if ($lines) { toObj $lines }
} | Format-Table
Explanation:
Uses ForEach-Object (%) with separate begin, process, and end blocks.
The -begin, executed once at the beginning:
Defines helper function toObj() that converts a block of contiguous nonempty input lines to a single custom object.
toObj() splits an array of lines into an array of contiguous key-value elements, converts that array to a hashtable, which is then converted to a custom object.
Initializes array $lines, which will store the lines of a single block of contiguous nonempty input lines
The -process block, executed for each input line:
If the input line at hand is nonempty: Adds it to the current block of contiguous nonempty input lines stored in array $lines.
Otherwise: Submits the current block to toObj() for conversion to a custom object, and then resets the $lines array to start the next block. In effect, toObj() is invoked for each paragraph (run of nonempty lines).
The -end block, executed once at the end:
Submits the last paragraph to toObj() for conversion to a custom object.
Finally, the resulting array of custom objects is passed to Format-Table.

Related

Powershell - How to split a string based on characters?

I have a list of pdf filenames that need to be parsed and ultimately sent to a sql table, with the parse out pieces each in their own column. How would I split based on a dash '-' and ultimately get it into a table.
What cmdlets would you start with to split on a character? I need to split based on the dash '-'.
Thanks for the help.
Example File Names:
tester-2458-full_contact_snapshot-20200115_1188.pdf
tester-2458-limited_contact_snapshot-20200119_9330.pdf
Desired Results:
There is also a -split operator.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_split
basic example:
if you have file names in $FilePaths array.
foreach($filepath in $FilePaths)
{
$parts = $filepath -split '-';
[pscustomobject]#{"User" = $parts[0]; "AppID" = $parts[1]; "FileType" = $parts[2]; "FilePath"=$filepath }
}
Use $variable.split('-') which will return a string array with a length equal to however many elements are produced by the split operation.
yet another way is to use regex & named capture groups. [grin]
what it does ...
creates a set of file name strings to work with
when ready to use real data, remove the entire #region/#endregion block and use either (Get-ChildItem).Name or another method that gives you plain strings.
iterates thru the collection of file name strings
uses $Null = to suppress the False/True output of the -match call
does a regex match with named capture groups
uses the $Match automatic variable to plug the captured values into the desired properties of a [PSCustomObject]
sends that PSCO out to the $Results collection
displays that on screen
sends it to a CSV for later use
the code ...
#region >>> fake reading in a list of file names
# in real life, use (Get-ChildItem).Name
$InStuff = #'
tester-2458-full_contact_snapshot-20200115_1188.pdf
tester-2458-limited_contact_snapshot-20200119_9330.pdf
'# -split [System.Environment]::NewLine
#endregion >>> fake reading in a list of file names
$Results = foreach ($IS_Item in $InStuff)
{
$Null = $IS_Item -match '^(?<User>.+)-(?<AppId>.+)-(?<FileType>.+)-(?<Date>.+)\.pdf$'
[PSCustomObject]#{
User = $Matches.User
AppId = $Matches.AppId
FileType = $Matches.FileType
Date = $Matches.Date
FileName = $IS_Item
}
}
# display on screen
$Results
# send to CSV file
$Results |
Export-Csv -LiteralPath "$env:TEMP\JM1_-_FileReport.csv" -NoTypeInformation
output to screen ...
User : tester
AppId : 2458
FileType : full_contact_snapshot
Date : 20200115_1188
FileName : tester-2458-full_contact_snapshot-20200115_1188.pdf
User : tester
AppId : 2458
FileType : limited_contact_snapshot
Date : 20200119_9330
FileName : tester-2458-limited_contact_snapshot-20200119_9330.pdf
content of the C:\Temp\JM1_-_FileReport.csv file ...
"User","AppId","FileType","Date","FileName"
"tester","2458","full_contact_snapshot","20200115_1188","tester-2458-full_contact_snapshot-20200115_1188.pdf"
"tester","2458","limited_contact_snapshot","20200119_9330","tester-2458-limited_contact_snapshot-20200119_9330.pdf"

Converting string with object layout to object

Some objects has been saved to a txt.file
looking like this:
#{flightNumber=01; flightDate=2010-01-10; flightIdentification=201001}
#{flightNumber=01; flightDate=2010-01-10; flightIdentification=201002}
and I'm trying to read them in another program and convert them back into objects. What bothers me is that it understands each of the "objects" as a string and I have been unable to cast it into an object.
$list = Get-Content -Path 'C:\Users\XXXXX\Downloads\TemplateObject.txt'
foreach (#object in $list) {
Write-Host $object.flightNumber
}
From what I've shown, I would expect to see 2 different objects with the variables flightNumber, flightDate and flightIdentification
I've tried piping it by using ConvertFrom-StringData
I've tried casting to an object
I expect 2 separate objects containing 3 variables in each.
Don't pipe objects directly to files!
As has been pointed out, take advantage of built-in options for serialization to disk, like ConvertTo-Csv/Export-Csv for flat objects, ConvertTo-Json or Export-Clixml for more complex objects.
As a one-off thing, if you need to recover and re-encode this data, you could use the regex -replace operator to add quotes around the values, at which point the parser should accept them as hashtable entries and you can cast it to an object:
$string = '#{flightNumber=01; flightDate=2010-01-10; flightIdentification=201001}'
# Place double-quotes around anything found between a `=` and `;` or `}`
$quotedString = $string -replace '(?<=\=)([^=;}]+)(?=\s*(?:;|}))', '"$1"'
# Parse the resulting string as if it was PowerShell code
$errors = #()
$objectAST = [System.Management.Automation.Language.Parser]::ParseInput($quotedString, [ref]$null,[ref]$errors)
$objects = if(-not $errors){
# This is pretty dangerous, you should NEVER do this in a production script
$objectAST.GetScriptBlock.Invoke() |ForEach-Object {
[pscustomobject]$_
}
}
# This variable now contains the re-animated objects
$objects
You can convert a string to a hashtable using convertfrom-stringdata after some manipulation:
$a = '#{flightNumber=01; flightDate=2010-01-10; flightIdentification=201001}'
$a = $a -replace '#{' -replace '}' -replace ';',"`n" | ConvertFrom-StringData
[pscustomobject]$a
flightNumber flightIdentification flightDate
------------ -------------------- ----------
01 201001 2010-01-10

Constructing an array from a text file in Powershell

I have an annoying report output (curse be to HP) that is not something I can shape by a query - it's all or nothing more or less. I would like to take two lines from each "chunk" of output and construct an array from this. I figured it would be a simple split() operation but no such luck. A sample of the output is as so:
Medium identifier : 1800010a:54bceddd:1d8c:0007
Medium label : [ARJ170L6] ARJ170L6
Location : [TapeLibrary: 24]
Medium Owner : wfukut01
Status : Poor
Blocks used [KB] : 2827596544
Blocks total [KB] : 2827596544
Usable space [KB] : 1024
Number of writes : 16
Number of overwrites : 4
Number of errors : 0
Medium initialized : 19 January 2015, 11:43:32
Last write : 26 April 2016, 21:02:12
Last access : 26 April 2016, 21:02:12
Last overwrite : 24 April 2016, 04:48:55
Protected : Permanent
Write-protected : No
Medium identifier : 1800010a:550aa81e:3a0c:0006
Medium label : [ARJ214L6] ARJ214L6
Location : External
Medium Owner : wfukut01
Status : Poor
Blocks used [KB] : 2904963584
Blocks total [KB] : 2904963584
Usable space [KB] : 0
Number of writes : 9
Number of overwrites : 7
Number of errors : 0
Medium initialized : 19 March 2015, 10:42:45
Last write : 30 April 2016, 22:14:19
Last access : 30 April 2016, 22:14:19
Last overwrite : 29 April 2016, 13:41:35
Protected : Permanent
Write-protected : No
What would be ideal is if the final output of this work would create an array somewhat similar to this:
Location UsableSpace
--------- -------------
External 0
TapeLibrary 1024
So I can (for example) query the output so that I can do operations on the data within the array:
$myvar | where-object { $_.Location -eq "TapeLibrary" }
Perhaps there are better approaches? I would be more than happy to hear them!
If the command is not a Powershell cmdlet like Kolob Canyon's answer then you would need to parse the text. Here's an inelegant example using -match and regex to find the lines with Location and Usable space [KB] and find the word characters after the colon.
((Get-Content C:\Example.txt -Raw) -split 'Medium identifier') | ForEach-Object {
[void]($_ -match 'Location\s+:\s(.*?(\w+).*)\r')
$Location = #($Matches.values | Where-Object {$_ -notmatch '\W'})[0]
[void]($_ -match 'Usable\sspace\s\[KB\]\s+:\s(.*?(\w+).*)\r')
$UsableSpace = #($Matches.values | Where-Object {$_ -notmatch '\W'})[0]
if ($Location -or $UsableSpace){
[PSCustomObject]#{
Location = $Location
UsableSpace = $UsableSpace
}
}
}
As this is extremely fragile and inelegant, it's much better to interact with an object where ever possible.
Assuming the data is as regular as it looks, you could use multiple assignment to extract the data from the array as in:
$data = 1, 2, "ignore me", 3, 10, 22, "ignore", 30
$first, $second, $null, $third, $data = $data
where the first, second and fourth array elements go into the variables, "ignore me" gets discarded in $null and the remaining data goes back into data. In your case, this would look like:
# Read the file into an array
$data = Get-Content data.txt
# Utility to fix up the data row
function FixUp ($s)
{
($s -split ' : ')[1].Trim()
}
# Loop until all of the data is processed
while ($data)
{
# Extract the current record using multiple assignment
# $null is used to eat the blank lines
$identifier,$null,$label,$location,$owner,$status,
$used,$total,$space,$writes,$overwrites,
$errors, $initialized, $lastwrite, $lastaccess,
$lastOverwrite, $protected, $writeprotected,
$null, $null, $data = $data
# Convert it into a custom object
[PSCustomObject] [ordered] #{
Identifier = fixup $identifier
Label = fixup $label
location = fixup $location
Owner = fixup $owner
Status = fixup $status
Used = fixup $used
Total = fixup $total
Space = fixup $space
Write = fixup $writes
OverWrites = fixup $overwrites
Errors = fixup $errors
Initialized = fixup $initialized
LastWrite = [datetime] (fixup $lastwrite)
LastAccess = [datetime] (fixup $lastaccess)
LastOverWrite = [datetime] (fixup $lastOverwrite)
Protected = fixup $protected
WriteProtected = fixup $writeprotected
}
}
Once you have the data extracted, you can format it any way you want
That looks a very regular pattern, so I'd say there are three typical approaches to this.
First: your own bucket-fill-trigger-empty parser, load lines in until you reach the next trigger ("Medium identifier"), then empty out the bucket to the pipeline and start a new one.
Something like:
$bucket = #{}
foreach ($line in Get-Content -LiteralPath C:\path\data.txt)
{
# if full, empty bucket to pipeline
if ($line -match '^Medium identifier')
{
[PSCustomObject]$bucket
$bucket = #{}
}
# add line to bucket (unless it's blank)
if (-not [string]::IsNullOrWhiteSpace($line))
{
$left, $right = $line.Split(':', 2)
$bucket[$left.Trim()] = $right.Trim()
}
}
# empty last item to pipeline
[PSCustomObject]$bucket
Adjust to taste for identifying numbers, dates, etc.
Second: a multiline regex: I tried, but can't. It would look something like:
# Not working, but for example:
$r = #'
Medium identifier : (?<MediumIdentifier>.*)
\s*
Write-protected : (?<WriteProtected>.*)
Blocks used [KB] : (?<BlockesUsed>.*)
Medium label : (?<MediumLabel>.*)
Last write : (?<LastWrite>.*)
Medium Owner : (?<MediumOwner>.*)
Usable space [KB] : (?<UsableSpaceKB>.*)
Number of overwrites : (?<NumberOfOverwrites>.*)
Last overwrite : (?<LastOverwrite>.*)
Medium identifier : (?<MediumIdentifier>.*)
Blocks total [KB] : (?<BlocksTotalKB>.*)
Number of errors : (?<NumberOfErrors>.*)
Medium initialized : (?<MediumInitialized>.*)
Status : (?<Status>.*)
Location : (?<Location>.*)
Protected : (?<Protected>.*)
Number of writes : (?<NumberOfWrites>.*)
Last access : (?<LastAccess>.*)
\s*
'#
[regex]::Matches((get-content C:\work\a.txt -Raw), $r,
[System.Text.RegularExpressions.RegexOptions]::IgnoreCase +
[System.Text.RegularExpressions.RegexOptions]::Singleline
)
Third: ConvertFrom-String - http://www.lazywinadmin.com/2014/09/powershell-convertfrom-string-and.html or https://blogs.technet.microsoft.com/ashleymcglone/2016/09/14/use-the-new-powershell-cmdlet-convertfrom-string-to-parse-klist-kerberos-ticket-output/ then after you made the template
Get-Content data.txt | ConvertFrom-String -TemplateFile .\template.txt
The easiest way is to use the command itself and select certain properties. If the command is a powershell cmdlet, it should return an Object.
$output = Some-HPCommand | select 'Medium label', 'Location'
Then you can access specific properties:
$output.'Medium label'
$output.Location
If you can provide the exact command, I can write this more accurately.
The biggest issue when people are learning powershell is they treat output like a String... Everything in PowerShell is object-oriented, and once you begin to think in terms of Objects, it becomes much easier to process data; in other words, always try to handle output as objects or arrays of objects. It will make your life a hell of a lot easier.
If each section is in the same format, i.e. the Usable space section is always 5 lines down from the location then you can use the Select-String in combination with the context parameter. Something like this:
Select-String .\your_file.txt -Pattern '(?<=Location\s*:\s).*' -Context 0, 5 | % {
New-Object psobject -Property #{
Location = (($_.Matches[0] -replace '\[|\]', '') -split ':')[0]
UsableSpace = ($_.Context.PostContext[4] -replace '^\D+(\d+)$', '$1' )
}
}

Powershell - Iterate through variables dynamically

I am importing a CSV file with two records per line, "Name" and "Path".
$softwareList = Import-Csv C:\Scripts\NEW_INSTALLER\softwareList.csv
$count = 0..($softwareList.count -1)
foreach($i in $count){
Write-Host $softwareList[$i].Name,$softwareList[$i].Path
}
What I am trying to do is dynamically assign the Name and Path of each record to a WPFCheckbox variable based on the $i variable. The names for these checkboxes are named something such as WPFCheckbox0, WPFCheckbox1, WPFCheckbox2 and so on. These objects have two properties I planned on using, "Command" to store the $SoftwareList[$i].path and "Content" to store the $SoftwareList[$i].Name
I cannot think of a way to properly loop through these variables and assign the properties from the CSV to the properties on their respective WPFCheckboxes.
Any suggestions would be very appreciated.
Invoke-Expression is one way, though note Mathias' commented concerns on the overall approach.
Within your foreach loop, you can do something like:
invoke-expression "`$WPFCheckbox$i`.Command = $($SoftwareList[$i].Path)"
invoke-expression "`$WPFCheckbox$i`.Content= $($SoftwareList[$i].Name)"
The back-tick ` just before the $WPFCheckBox prevents what would be an undefined variable from being immediately evaluated (before the expression is invoked), but the $I is. This gives you a string with your $WPFCheckbox1, to which you then append the property names and values. The $SoftwareList values are immediately processed into the raw string.
The Invoke-Expression then evaluates and executes the entire string as if it were a regular statement.
Here's a stand-alone code snippet to play with:
1..3 |% {
invoke-expression "`$MyVariable$_` = New-Object PSObject"
invoke-expression "`$MyVariable$_` | add-member -NotePropertyName Command -NotePropertyValue [String]::Empty"
invoke-expression "`$MyVariable$_`.Command = 'Path #$_'"
}
$MyVariable1 | Out-String
$MyVariable2 | Out-String
$MyVariable3 | Out-String
As a side note (since I can't comment yet on your original question,) creating an array just to act as iterator through the lines of the file is really inefficient. There are definitely better ways to do that.

Reading strings from text files using switch -regex returns null element

Question:
The intention of my script is to filter out the name and phone number from both text files and add them into a hash table with the name being the key and the phone number being the value.
The problem I am facing is
$name = $_.Current is returning $null, as a result of which my hash is not getting populated.
Can someone tell me what the issue is?
Contents of File1.txt:
Lori
234 east 2nd street
Raleigh nc 12345
9199617621
lori#hotmail.com
=================
Contents of File2.txt:
Robert
2531 10th Avenue
Seattle WA 93413
2068869421
robert#hotmail.com
Sample Code:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' { $name = $_.current}
'^\d{10}' {
$phone = $_.current
$hash.Add($name,$phone)
$name=$phone=$null
}
default
{
write-host "Nothing matched"
}
}
$hash
Remove the current property reference from $_:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' {
$name = $_
}
'^\d{10}' {
$phone = $_
$hash.Add($name, $phone)
$name = $phone = $null
}
default {
Write-Host "Nothing matched"
}
}
$hash
Mathias R. Jessen's helpful answer explains your problem and offers an effective solution:
it is automatic variable $_ / $PSItem itself that contains the current input object (whatever its type is - what properties $_ / $PSItem has therefore depends on the input object's specific type).
Aside from that, there's potential for making the code both less verbose and more efficient:
# Initialize the output hashtable.
$hash = #{}
# Create the regex that will be used on each input file's content.
# (?...) sets options: i ... case-insensitive; m ... ^ and $ match
# the beginning and end of every *line*.
$re = [regex] '(?im)^([a-z]+|\d{10})$'
# Loop over each input file's content (as a whole, thanks to -Raw).
Get-Content -Raw File*.txt | foreach {
# Look for name and phone number.
$matchColl = $re.Matches($_)
if ($matchColl.Count -eq 2) { # Both found, add hashtable entry.
$hash.Add($matchColl.Value[0], $matchColl.Value[1])
} else {
Write-Host "Nothing matched."
}
}
# Output the resulting hashtable.
$hash
A note on the construction of the .NET [System.Text.RegularExpressions.Regex] object (or [regex] for short), [regex] '(?im)^([a-z]+|\d{10})$':
Embedding matching options IgnoreCase and Multiline as inline options i and m directly in the regex string ((?im) is convenient, in that it allows using simple cast syntax ([regex] ...) to construct the regular-expression .NET object.
However, this syntax may be obscure and, furthermore, not all matching options are available in inline form, so here's the more verbose, but easier-to-read equivalent:
$re = New-Object regex -ArgumentList '^([a-z]+|\d{10})$', 'IgnoreCase, Multiline'
Note that the two options must be specified comma-separated, as a single string, which PowerShell translates into the bit-OR-ed values of the corresponding enumeration values.
other solution, use convertfrom-string
$template=#'
{name*:Lori}
{street:234 east 2nd street}
{city:Raleigh nc 12345}
{phone:9199617621}
{mail:lori#hotmail.com}
{name*:Robert}
{street:2531 10th Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
{name*:Robert}
{street:2531 Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
'#
Get-Content -Path "c:\temp\file*.txt" | ConvertFrom-String -TemplateContent $template | select name, phone