Format table from output of a string - powershell

I have tried different ways but not able to format data into table
$str1 = "First string"
$str2 = "Sec string"
$str3 = "third str"
$str4 = "fourth string"
$str = "$str1 $str2 `r`n"
$str+= "$str3 $str4"
write-host $str | Format-Table
I am looking to create output like below:
First string Sec string
third str fourth string

In order to use Format-Table as intended, you need objects with properties rather than mere strings:
$str -split "`r`n" | ForEach-Object {
# Initialize a custom object whose properties will reflect
# the input line's tokens (column values).
$obj = New-Object PSCustomObject; $i = 0
# Add each whitespace-separated token as a property.
foreach ($token in -split $_) {
Add-Member -InputObject $obj -NotePropertyName ('col' + ++$i) -NotePropertyValue $token
}
# Output the custom object.
$obj
} | Format-Table -HideTableHeaders
$str -split "`r`n" splits the multi-line string into individual lines and sends them through the pipeline one by one.
The ForEach-Object command constructs a custom object from each line whose properties are the whitespace-separated tokens on the line, as described in the comments; the property names - which don't matter for the output - are auto-generated as col1, col2, ...
Note: This does not match your desired output exactly in that each space (run of whitespace) is treated as a separator. If you wanted to treat the original $str1, $str2, ... variable values (e.g., First string) each as a single column value, you'd have to make assumptions about how to tokenize the line.
For instance, if the assumption is that 2 consecutive words form a single value, replace -split $_ above with $_ -split '(\w+ \w+) ?' -ne ''
If you didn't want to rely on assumptions, you'd have to construct your input strings with embedded quoting so as to unambiguously indicate token boundaries (the code would then have to be modified to parse the embedded quoting correctly).
Format-Table then displays the custom objects in tabular form, with columns properly aligned; -HideTableHeaders suppresses the header line (the auto-generated property names).
With your sample input, the above yields the following, produced without -HideTableHeaders so as to better illustrate what the code does:
col1 col2 col3 col4
---- ---- ---- ----
First string Sec string
third str fourth string
Ditto, but with the 2-consecutive-words splitting logic:
col1 col2
---- ----
First string Sec string
third str fourth string
As for what you tried:
Do not use Write-Host to produce data output: Write-Host output (by default) goes to the console and bypasses the pipeline, so that Format-Table receives no input and has no effect here.
That said, even if Format-Table did receive input (by using $str by itself, without Write-Host, i.e.: $str | Format-Table), it would have no (visible) effect on strings, which are always rendered as-is.

Related

Insert blank columns into csv with Powershell

In my script, I am building a custom Powershell object which will be sent to Export-Csv. The receiving party has required that I include some blank columns (no data and no header) and I have no idea how to do that.
If the object looks like this:
$obj = [PSCustomObject][ordered]#{
EMPLOYER_EIN = '123456'
ACTION_CODE = 1
LAST_NAME = Smith
FIRST_NAME = John
MIDDLE_INITIAL = $null
EMPLOYEE_SSN = '111-11-1111'
}
How can I have the resulting .csv file's first row look like this:
EMPLOYER_EIN,ACTION_CODE,,LAST_NAME,FIRST_NAME,MIDDLE_INITIAL,,EMPLOYEE_SSN
Put another way, after I run Export-Csv, I want the file to look like this when opened in Excel:
EMPLOYER_EIN
ACTION_CODE
LAST_NAME
FIRST_NAME
MIDDLE_INITIAL
EMPLOYEE_SSN
123456
1
Smith
John
111-11-1111
Note the extra columns between action_code/last_name and middle_initial/employee_ssn. I am using PS 5.1 but could use 7 if necessary.
As a test, I created a CSV test.csv with fields A,B, and C, and put a couple of lines of values:
"A","B","C"
1,2,3
4,5,6
I then executed the sequence of commands
Import-CSV -path Test.csv | Select-Object -Prop A," ",B,C | Export-CSV -Path test2.csv
and looked at the resultant test2.csv, which contained
#TYPE Selected.System.Management.Automation.PSCustomObject
"A"," ","B","C"
"1",,"2","3"
"4",,"5","6"
I believe that this is going to be the closest you'll get without manually processing the CSV as a text file.
This is essentially what Santiago Squarzon was suggesting in the comments.
If you need multiple "blank" columns, each one will have to have a header with a different non-zero number of spaces.
I suggest:
constructing the object with blank dummy properties with a shared name prefix, such as BLANK_, followed by a sequence number (the property names must be unique)
initially piping to ConvertTo-Csv, which allows use of a -replace operation to replace the dummy property names with empty strings in the first output line (the header line).
the result - which already is in CSV format - can then be saved to a CSV file with Set-Content.
$obj = [PSCustomObject] #{
EMPLOYER_EIN = '123456'
ACTION_CODE = 1
BLANK_1 = $null # first dummy property
LAST_NAME = 'Smith'
FIRST_NAME = 'John'
MIDDLE_INITIAL = $null
BLANK_2 = $null # second dummy property
EMPLOYEE_SSN = '111-11-1111'
}
$first = $true
$obj |
ConvertTo-Csv |
ForEach-Object {
if ($first) { # header row: replace dummy property names with empty string
$first = $false
$_ -replace '\bBLANK_\d+'
}
else { # data row: pass through
$_
}
} # pipe to Set-Content as needed.
Output (note the blank column names after ACTION CODE and MIDDLE_INITIAL):
"EMPLOYER_EIN","ACTION_CODE","","LAST_NAME","FIRST_NAME","MIDDLE_INITIAL","","EMPLOYEE_SSN"
"123456","1",,"Smith","John",,,"111-11-1111"

Powershell: Import-csv, rename all headers

In our company there are many users and many applications with restricted access and database with evidence of those accessess. I don´t have access to that database, but what I do have is automatically generated (once a day) csv file with all accessess of all my users. I want them to have a chance to check their access situation so i am writing a simple powershell script for this purpose.
CSV:
user;database1_dat;database2_dat;database3_dat
john;0;0;1
peter;1;0;1
I can do:
import-csv foo.csv | where {$_.user -eq $user}
But this will show me original ugly headres (with "_dat" suffix). Can I delete last four characters from every header which ends with "_dat", when i can´t predict how many headers will be there tomorrow?
I am aware of calculated property like:
Select-Object #{ expression={$_.database1_dat}; label='database1' }
but i have to know all column names for that, as far as I know.
Am I convicted to "overingeneer" it by separate function and build whole "calculated property expression" from scratch dynamically or is there a simple way i am missing?
Thanks :-)
Assuming that file foo.csv fits into memory as a whole, the following solution performs well:
If you need a memory-throttled - but invariably much slower - solution, see Santiago Squarzon's helpful answer or the alternative approach in the bottom section.
$headerRow, $dataRows = (Get-Content -Raw foo.csv) -split '\r?\n', 2
# You can pipe the result to `where {$_.user -eq $user}`
ConvertFrom-Csv ($headerRow -replace '_dat(?=;|$)'), $dataRows -Delimiter ';'
Get-Content -Raw reads the entire file into memory, which is much faster than reading it line by line (the default).
-split '\r?\n', 2 splits the resulting multi-line string into two: the header line and all remaining lines.
Regex \r?\n matches a newline (both a CRLF (\r\n) and a LF-only newline (\n))
, 2 limits the number of tokens to return to 2, meaning that splitting stops once the 1st token (the header row) has been found, and the remainder of the input string (comprising all data rows) is returned as-is as the last token.
Note the $null as the first target variable in the multi-assignment, which is used to discard the empty token that results from the separator regex matching at the very start of the string.
$headerRow -replace '_dat(?=;|$)'
-replace '_dat(?=;|$)' uses a regex to remove any _dat column-name suffixes (followed by a ; or the end of the string); if substring _dat only ever occurs as a name suffix (not also inside names), you can simplify to -replace '_dat'
ConvertFrom-Csv directly accepts arrays of strings, so the cleaned-up header row and the string with all data rows can be passed as-is.
Alternative solution: algorithmic renaming of an object's properties:
Note: This solution is slow, but may be an option if you only extract a few objects from the CSV file.
As you note in the question, use of Select-Object with calculated properties is not an option in your case, because you neither know the column names nor their number in advance.
However, you can use a ForEach-Object command in which you use .psobject.Properties, an intrinsic member, for reflection on the input objects:
Import-Csv -Delimiter ';' foo.csv | where { $_.user -eq $user } | ForEach-Object {
# Initialize an aux. ordered hashtable to store the renamed
# property name-value pairs.
$renamedProperties = [ordered] #{}
# Process all properties of the input object and
# add them with cleaned-up names to the hashtable.
foreach ($prop in $_.psobject.Properties) {
$renamedProperties[($prop.Name -replace '_dat(?=.|$)')] = $prop.Value
}
# Convert the aux. hashtable to a custom object and output it.
[pscustomobject] $renamedProperties
}
You can do something like this:
$textInfo = (Get-Culture).TextInfo
$headers = (Get-Content .\test.csv | Select-Object -First 1).Split(';') |
ForEach-Object {
$textInfo.ToTitleCase($_) -replace '_dat'
}
$user = 'peter'
Get-Content .\test.csv | Select-Object -Skip 1 |
ConvertFrom-Csv -Delimiter ';' -Header $headers |
Where-Object User -EQ $user
User Database1 Database2 Database3
---- --------- --------- ---------
peter 1 0 1
Not super efficient but does the trick.

Powershell output in one line

I'm pretty new when it comes to scripting with powershell (or in general whe it comes to scripting). The problem that i have, is that i got a bunch of variables i want to output in one line. Here is not the original but simplified code:
$a = 1
$b = 2
$c = $a; $b;
Write-output $c
The output looks like this:
1
2
You may guess how i want the output to look like:
12
I've searched the net to get a solution but nothing seem to work. What am i doing wrong?
Right now you're only assigning $a to $c and then outputting $b separately - use the #() array subexpression operator to create $c instead:
$c = #($a; $b)
Then, use the -join operator to concatenate the two values into a single string:
$c -join ''
You can make things easier on yourself using member access or Select-Object to retrieve property values. Once the values are retrieved, you can them manipulate them.
It is not completely clear what you really need, but the following is a blueprint of how to get the desired system data from your code.
# Get Serial Number
$serial = Get-CimInstance CIM_BIOSElement | Select-Object -Expand SerialNumber
# Serial Without Last Digit
$serialMinusLast = $serial -replace '.$'
# First 7 characters of Serial Number
# Only works when serial is 7 or more characters
$serial.Substring(0,7)
# Always works
$serial -replace '(?<=^.{7}).*$'
# Get Model
$model = Get-CimInstance Win32_ComputerSystem | Select-Object -Expand Model
# Get First Character and Last 4 Characters of Model
$modelSubString = $model -replace '^(.).*(.{4})$','$1$2'
# Output <model substring - serial number substring>
"{0}-{1}" -f $modelSubString,$serialMinusLast
# Output <model - serial number>
"{0}-{1}" -f $model,$serial
Using the syntax $object | Select-Object -Expand Property will retrieve the value of Property only due to the use of -Expand or -ExpandProperty. You could opt to use member access, which uses the syntax $object.Property, to achieve the same result.
If you have an array of elements, you can use the -join operator to create a single string of those array elements.
$array = 1,2,3,'go'
# single string
$array -join ''
The string format operator -f can be used to join components into a single string. It allows you to easily add extra characters between the substrings.

How to convert filecontent using powershell

I have a log file with a weird format that I would like to convert to a table. The format is that each line contains multiple keyvalue pairs (same pairs on each row). I want to convert these rows so that each property becomes a column in a table containing the value from the row.
Note that the original log file contains 39 properies on each row and the log file is about 80MB.
Example rows:
date=2019-12-02 srcip=8.8.8.8 destip=8.8.4.4 srcintf="port2"
date=2019-12-01 srcip=8.8.8.8 destip=8.8.4.4 srcintf="xyz abc"
date=2019-12-03 srcip=8.8.8.8 destip=8.8.4.4 srcintf="port2"
date=2019-12-05 srcip=8.8.8.8 destip=8.8.4.4 srcintf="port2"
date=2019-12-07 srcip=8.8.8.8 destip=8.8.4.4 srcintf="port2"
I have tried:
Get-Content .\testfile.log | select -First 10 | ConvertFrom-String | select p1, p2, p3 | ft | Format-Wide
But this will not break out the property name to the column name. So in this example i want P1 to be date, p2 srcip, and p3 destip and that the first part of each value is removed.
Anyone have any tips or creative ideas how to convert this to a table?
ConvertFrom-String provides separator-based parsing as well as heuristics-based parsing based on templates containing example values. The separator-based parsing applies automatic type conversions you cannot control, and the template language is poorly documented, with the exact behavior hard to predict - it's best to avoid this cmdlet altogether. Also note that it's not available in PowerShell [Core] v6+.
Instead, I suggest an approach based on the switch statement[1] and the -split operator to create a collection of custom objects ([pscustomobject]) representing the log lines:
# Use $objects = switch ... to capture the generated objects in a variable.
switch -File .\testfile.log { # Loop over all file lines
default {
$oht = [ordered] #{ } # Define an aux. ordered hashtable
foreach ($keyValue in -split $_) { # Loop over key-value pairs
$key, $value = $keyValue -split '=', 2 # Split pair into key and value
$oht[$key] = $value -replace '^"|"$' # Add to hashtable with "..." removed
}
[pscustomobject] $oht # Convert to custom object and output.
}
}
Note:
The above assumes that your values have no embedded spaces; if they do, more work is needed - see next section.
To capture the generated custom objects in a variable, simply use $objects = switch ...
With two ore more log lines, $objects becomes an [object[]] array of [pscustomobject] instances. If you want to ensure that $objects also becomes an array even if there happens to be just one log line, use [array] $objects = switch ... ([array] is effectively the same as [object[]]).
To directly send the output objects through the pipeline to other cmdlets, enclose the switch statement in & { ... }
With your sample input, this yields:
date srcip destip srcintf
---- ----- ------ -------
2019-12-02 8.8.8.8 8.8.4.4 port2
2019-12-01 8.8.8.8 8.8.4.4 port2
2019-12-03 8.8.8.8 8.8.4.4 port2
2019-12-05 8.8.8.8 8.8.4.4 port2
2019-12-07 8.8.8.8 8.8.4.4 port2
Variant with support for values with embedded spaces inside "..." (e.g., srcintf="port 2"):
switch -file .\testfile.log {
default {
$oht = [ordered] #{ }
foreach ($keyValue in $_ -split '(\w+=(?:[^"][^ ]*|"[^"]*"))' -notmatch '^\s*$') {
$key, $value = $keyValue -split '=', 2
$oht[$key] = $value -replace '^"|"$'
}
[pscustomobject] $oht
}
}
Note that there's no support for embedded escaped " instances (e.g, srcintf="port \"2\"" won't work).
Explanation:
$_ -split '(\w+=(?:[^"][^ ]*|"[^"]*"))' splits by a regex that matches key=valueWithoutSpaces and key="value that may have spaces" tokens and, by virtue of enclosing the expression in (...) (creating a capture group), includes these "separators" in the tokens that -split outputs (by default, separators aren't included).
-notmatch '^\s*$' then weeds out empty and all-spaces tokens from the result (the "data tokens", which aren't of interest in our case), leaving effectively just the key-value pairs.
$key, $value = $keyValue -split '=', 2 splits the given key-value token by = into at most 2 tokens, and uses a destructuring assignment to assign the key and the value to separate variables.
$oht[$key] = $value -replace '^"|"$' adds an entry to the aux. hashtable with the key and value at hand, where -replace '^"|"$' uses the -replace operator to remove " from the beginning and end of the value, if present.
[1] switch -File is a flexible and much faster alternative to processing a file line by line with a combination of Get-Content and ForEach-Object.
So what you could do is cut each line into a hashtable of key value pairs passing those to ConvertFrom-StringData instead. There is a couple of caveats with this approach. In keeping it simple your source data is space delimited. This would break if you real data contained spaces (which can be mitigated.) Other obvious caveat is you can't guarantee property order.
Get-Content c:\temp\so.txt | ForEach-Object{
[PSCustomObject](($_ -split " ") -join "`r`n" | ConvertFrom-StringData)
} | Select-Object date, srcip, destip, srcintf
Output:
date srcip destip srcintf
---- ----- ------ -------
2019-12-02 8.8.8.8 8.8.4.4 "port2"
2019-12-01 8.8.8.8 8.8.4.4 "port2"
2019-12-03 8.8.8.8 8.8.4.4 "port2"
2019-12-05 8.8.8.8 8.8.4.4 "port2"
2019-12-07 8.8.8.8 8.8.4.4 "port2"
OK, for the purposes of discussion, I am going to assume the following:
The data is in a file PSDATA.TXT
There are no spaces in the data other than the spaces separating the name-value pairs.
It is acceptable for the resulting tabular data to treat all the values as strings.
Given that...
Get-Content -Path PSDATA.TXT |
ForEach-Object {$_ -replace ' ','";' -replace '=','="' -replace '""','"'} |
ForEach-Object {New-Object PSObject -Property (Invoke-Expression ("[Ordered]#{{{0}}}" -f $_))}
... will generate a table where each line in the file becomes a PSObject with fields taking their names from the name in each name-value pair, and the associated value being the value of the field, as a string. If you're not using PowerShell v4 or later (I'm not sure about 3), you can omit the [Ordered], with the side effect of the order of the fields in the PSObject not necessarily being in the same order as in the file.
If you wanted to have an array of these PSObjects for further processing, you could wrap the whole line above in a variable assignment, e.g., $A=(«that whole thing above, on one line»), and if you wanted to send it to a CSV file, you could just add | Export-CSV -path NewCSVFile.CSV to the end.
I would prefer a datatable, so you easily can sort, filter, merge etc. the logfile:
$logFilePath = 'C:\test\test.log'
$dt = New-Object system.Data.DataTable
[void]$dt.Columns.Add('P1',[string]::empty.GetType() )
[void]$dt.Columns.Add('P2',[string]::empty.GetType() )
[void]$dt.Columns.Add('P3',[string]::empty.GetType() )
foreach( $line in [System.IO.File]::ReadLines($logFilePath) )
{
$tokenArray = $line -split '[= ]'
$row = $dt.NewRow()
$row.P1 = $tokenArray[1]
$row.P2 = $tokenArray[3]
$row.P3 = $tokenArray[5]
[void]$dt.Rows.Add( $row )
}
$dt

Method "split" not found in [Selected.System.Management.Automation.PSCustomObject]

in my script I want to read a csv-file into an array and split the text in the first column.
The file consists a table with 2 columns. In the first column there are the personal names with the short Usernames in brackets. In the second column are the position of the user.
User:
Hoch,Susane (HOCH05)
Albrecht, Melanie (ALBRE05)
Department:
Managment
Salesoffice
I read the first column in an array and want to split every char after the first "(". So the I have got "Hoch, Susanne" instead of "Hoch, Susane (HOCH05)".
I get the following error message:
[Selected.System.Management.Automation.PSCustomObject] contains no method with the name "Split".
The type of the variable "$value is:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
I can´t find my misstake.
Here is my code:
$Arrayusername_ads_unique = New-Object System.Collections.ArrayList
$AD_User = New-Object System.Collections.ArrayList
$AD_User_table = New-Object System.Collections.ArrayList
$username_AD = New-Object System.Collections.ArrayList
$Arrayusername_ads_unique = Get-Content -Path "C:\temp\Userabgleich\Liste-original\User_ADS-utf8.csv"
$Arrayusername_ads_unique | Out-File C:\temp\Userabgleich\output-temp\User_ADS-utf8.csv -Append -Encoding utf8
$AD_User = Import-CSV 'C:\temp\Userabgleich\output-temp\User_ADS-utf8.csv' -Delimiter ";" | sort User
$AD_User_table = $AD_User | Select-Object User
foreach ($value in $AD_User_table)
{
$value.GetType()
$username_AD = $value.Split("(")
}
You can modify your foreach loop to achieve the results:
foreach ($value in $AD_User_table)
{
($value.user -split "\(")[0]
}
I am splitting on the .user property of $value to retrieve the value you are after in string format. By default, $value is going to be a [PSCustomObject] with a property called User. I am retrieving index 0 ([0]) because your -split match will consume a line of output whether or not you choose to keep the output.
If you are only looping to retrieve this particular result, you can accomplish this without a loop using regex substitution and named captures:
$ad_user_table.user -replace "(?<Name>.*?)\(.*",'${Name}'
since you did not provide a proper CSV to test against, i made a guess at what it would look like. [grin]
what it does ...
uses the .Split() method to split on the (
takes the 1st result of that split
trims away any leading/trailing whitespace
sends the result to the $Names collection
displays the content of that collection
here's the code ...
# fake reading in a CSV file
# in real life, use Import-CSV
$InStuff = #'
User,Department
"Hoch,Susane (HOCH05)","Managment"
"Albrecht, Melanie (ALBRE05)","Salesoffice"
'# | ConvertFrom-Csv
$Names = foreach ($IS_Item in $InStuff)
{
$IS_Item.User.Split('(')[0].Trim()
}
$Names
output ...
Hoch,Susane
Albrecht, Melanie