PowerShell: Format-Table without headers - powershell

In a PowerShell script, I have some objects that I pass to the Format-Table CmdLet.
The output of my script looks like this:
Something...
Operation AttributeName AttributeValue
--------- ------------- --------------
Delete Member John Doe
Something else...
Since the meaning of the fields is pretty self-explanatory, I would like to remove the headers, the '---' separators and the blank lines at the beginning and at the end from the output of Format-Table.
I don't think that the CmdLet supports this (or at least if there's a parameter to do this I couldn't find it).
What would the best way to leave only the lines with the actual values from the output of Format-Table?

Try the -HideTableHeaders parameter to Format-Table:
gci | ft -HideTableHeaders
(I'm using PowerShell v2. I don't know if this was in v1.)

Try -ExpandProperty. For example, I use this for sending the clean variable to Out-Gridview -PassThru , otherwise the variable has the header info stored. Note that these aren't great if you want to return more than one property.
An example:
Get-ADUser -filter * | select name -expandproperty name
Alternatively, you could do this:
(Get-ADUser -filter * ).name

The -HideTableHeaders parameter unfortunately still causes the empty lines to be printed (and table headers appearently are still considered for column width). The only way I know that could reliably work here would be to format the output yourself:
| % { '{0,10} {1,20} {2,20}' -f $_.Operation,$_.AttributeName,$_.AttributeValue }

Here is how I solve this. I just pipe the output to Out-String and then pass that output to the .NET Trim function:
(gci | ft -HideTableHeaders | Out-String).Trim()
This will strip out the line breaks before and after the table.
You can also use TrimStart to just take care of the header's line break if you still want the trailing line breaks.
(gci | ft -HideTableHeaders | Out-String).TrimStart()

Another approach is to use ForEach-Object to project individual items to a string and then use the Out-String CmdLet to project the final results to a string or string array:
gci Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\CID | foreach { "CID Key {0}" -f $_.Name } | Out-String
#Result: One multi-line string equal to:
#"
CID Key HKEY_CLASSES_ROOT\CID\2a621c8a-7d4b-4d7b-ad60-a957fd70b0d0
CID Key HKEY_CLASSES_ROOT\CID\2ec6f5b2-8cdc-461e-9157-ffa84c11ba7d
CID Key HKEY_CLASSES_ROOT\CID\5da2ceaf-bc35-46e0-aabd-bd826023359b
CID Key HKEY_CLASSES_ROOT\CID\d13ad82e-d4fb-495f-9b78-01d2946e6426
"#
gci Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\CID | foreach { "CID Key {0}" -f $_.Name } | Out-String -Stream
#Result: An array of single line strings equal to:
#(
"CID Key HKEY_CLASSES_ROOT\CID\2a621c8a-7d4b-4d7b-ad60-a957fd70b0d0",
"CID Key HKEY_CLASSES_ROOT\CID\2ec6f5b2-8cdc-461e-9157-ffa84c11ba7d",
"CID Key HKEY_CLASSES_ROOT\CID\5da2ceaf-bc35-46e0-aabd-bd826023359b",
"CID Key HKEY_CLASSES_ROOT\CID\d13ad82e-d4fb-495f-9b78-01d2946e6426")
The benefit of this approach is that you can store the result to a variable and it will NOT have any empty lines.

I know it's 2 years late, but these answers helped me to formulate a filter function to output objects and trim the resulting strings. Since I have to format everything into a string in my final solution I went about things a little differently.
Long-hand, my problem is very similar, and looks a bit like this
$verbosepreference="Continue"
write-verbose (ls | ft | out-string) # this generated too many blank lines
Here is my example:
ls | Out-Verbose # out-verbose formats the (pipelined) object(s) and then trims blanks
My Out-Verbose function looks like this:
filter Out-Verbose{
Param([parameter(valuefrompipeline=$true)][PSObject[]]$InputObject,
[scriptblock]$script={write-verbose "$_"})
Begin {
$val=#()
}
Process {
$val += $inputobject
}
End {
$val | ft -autosize -wrap|out-string |%{$_.split("`r`n")} |?{$_.length} |%{$script.Invoke()}
}
}
Note1: This solution will not scale to like millions of objects(it does not handle the pipeline serially)
Note2: You can still add a -noheaddings option.
If you are wondering why I used a scriptblock here, that's to allow overloading like to send to disk-file or other output streams.

Related

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.

Using powershell only, sort a text file with 300 lines, first by string length and then, once in that length, set them all alphbetically

I am trying to do this as a one liner in powershell, so that I can move on to check these strings against a check string.
The trouble I am having is that no matter what I do, I can only set it by string length.
The following attempts failed to get the required result.One does text great, the other by length successfully. I have also tried to pipe them in but I believe that neither accept pipeline input.
Your help is appreciated as am new to powershell.
PS C:\Users\IEUser> Get-Content Desktop/dict.txt | Sort-Object
PS C:\Users\IEUser> Get-Content Desktop/dict.txt | Sort-Object -Property Length
Given the sample array $toSort:
$toSort = #(
'abcdefghwxyefg'
'abcdefghghijkl'
'abcdefghwxyefgabcdzx'
'abcdefghwxyefgabcdef'
'abcdzx'
'abcdef'
'abzxc'
'abcde'
'wxy'
'efg'
'abcdefgh'
'ijklmnop'
)
You can use Sort-Object to sort the array first by the Length property and then by alphabetical order like this:
$toSort | Sort-Object Length, { $_ }
Thanks Mathias for pointing it out, I was previously using { $_[0] } which would sort only the first char of each line.
Including how the actual answer should be:
Get-Content Desktop/dict.txt | Sort-Object -Property Length, { $_ } |
Out-File path/to/sortedDict.txt
If you want to have some fun with LINQ you can accomplish the same using first OrderBy to sort by Length and ThenBy alphabetical order:
[Linq.Enumerable]::ThenBy(
[Linq.Enumerable]::OrderBy($toSort, [Func[object, int]]{param($s) $s.Length }),
[Func[object, string]]{param($s) $s }
)

How can I replace a string inside a pipe?

I'm trying to replace some specific parts of a selected string but am only returning the length property. Here's my code:
Get-ChildItem "StartPath/Something/Files" -Recurse -File | Select "FullName | Foreach {$_.FullName -replace "StartPath",""} | Export-Csv "ResultPath.csv"
If I omit the foreach bit, this works in that it spits out the full path. I'd like to trim the full path as I'm iterating over tons of files. I'm trying to replace a bit of the path in the beginning of the string but my code above just spits out a CSV file with just string lengths.
Looks like:
"123"
"12"
"52"
and so forth.
The intended result would be a csv file with instead of:
StartPath/Something/Files1
StartPath/Something/Files2
I'd have
Something/Files1
Something/Files2
I've tried a number of things and can't seem to figure it out. Any help is appreciated.
If you pass a string to select / Select-Object (to its positionally implied -Property parameter), it must be a property name.[1]
If you want to perform open-ended operations and/or produce open-ended output for each input object, you must use the ForEach-Object cmdlet:
Get-ChildItem "StartPath/Something/Files" -Recurse -File |
ForEach-Object {
[pscustomobject] #{ FullName = $_.FullName -replace 'StartPath' }
} |
Export-Csv "ResultPath.csv"
Note the use of a [pscustomobject] wrapper that defines a FullName property, so that Export-Csv creates a CSV with that property as its (only) column.
If you pipe [string] instances directly to Export-Csv, their properties are serialized to the output file - and a [string]'s only (public) property is its length (.Length), which is what you saw.
[1] There's also a way to create properties dynamically, using so-called calculated properties, which are defined via hash tables.

Powershell hashtable does not write to file as expected - receive only "System.Collections" rows

Can someone please explain Why my first examples don't work, and why adding in a ForEach-Object solves the problem? Thanks in advance!
I parsed the return from a command into a hashtable (sample at end of post) and want to log the information to a file as part of my processing. I know that $ht.GetEnumerator() | Sort-Object Name will return the full hash to screen, sorted. However, once I try sending things to file, it breaks.
$ht | Add-Content log.txt
only logs a single row of System.Collections.Hashtable. So, I've also tried
$ht.GetEnumerator() | Sort-Object Name | Add-Content log.txt
and end up with rows of
System.Collections.DictionaryEntry
System.Collections.DictionaryEntry
System.Collections.DictionaryEntry
So then I tried to loop through and handle each individually with
foreach ($key in $ht.keys) {
Add-Content log.txt "$key : $ht.$key" }
and end up with
Server address : System.Collections.Hashtable.Server address
Client address : System.Collections.Hashtable.Client address
User name : System.Collections.Hashtable.User name
Solved with:
$ht.GetEnumerator() | Sort-Object Name |
ForEach-Object {"{0} : {1}" -f $_.Name,$_.Value} |
Add-Content log.txt
For reference, the hashtable sample:
$ht = #{
"Server address" = "server.net";
"Client address" = "10.20.121.153";
"User name" = "myuser"
}
Answering the why part, you obviously have a solution :)
In your first example
$ht | Add-Content log.txt
PowerShell takes $ht and tries to somehow convert it to a string so that it can be stored via Add-Content. Because there is no conversion defined for the hashtable, only the type name is returned from the conversion. Same as for example new-Object Random|Add-Content d:\log.txt. Again, only type name is written.
Next
$ht.GetEnumerator() | Sort-Object Name | Add-Content log.txt
is similar. GetEnumerator returns object that is used for iteration; objects of type System.Collections.DictionaryEntry are returned. Again, there is no conversion to string, so type names are returned.
Personally, I think PowerShell should be smart enough and help here. The question is "how?". Designers probably didn't want to hardcode the output. It might be "{key}: {value}" or "{key} = {value}", or "{key}/{value}", or ... The format is not clear, so they left it for us to decide and format as you did it with the foreach statement.
I agree with mjolinor... just not enough points to vote up... plus i'll add that you dont need the GetEnumerator
$ht | out-string | add-content log.txt
will do it.
Your first example does not work, or better, partially works, because you are trying to get a property value within the string. Normally, inside strings, the parser is able to resolve only direct variables (like $key). To resolve more complex variable you need parenthesis.
For the loop, this should work:
foreach ($key in $ht.keys) {
Add-Content log.txt "$key : $($ht.$key)" }
or even better
$ht.keys | %{ Add-Content log.txt "$_ : $($ht.$_)" }
As you can see in Microsoft documentation a hash table is simply a collection of name-value pairs.
So $ht is really System.Collections.Hashtable composed of System.Collections.DictionaryEntry.
A good way to use it is
foreach ($i in $ht.keys)
{
add-content log.txt ("{0} {1}" -f $i, $ht[$i])
}
How about:
$ht.GetEnumerator() | Sort-Object Name | out-string | Add-Content log.txt
You can write a "raw" dump of a hash table to an ASCII file using Out-File:
$data = #{
Name = "Something"
Type = "123"
}
$data | Out-File "myfile.txt" -Encoding ascii

Powershell: align right for a column value from Select-Object in Format-Table format

I have the following array value $outData with several columns. I am not sure how I align some columns right?
$outData | Select-Object `
Name `
#{Name="Freespace(byte)"; Expression={"{0:N0}" -f $_.FreeSpace}}, '
.... # other colums `
| Format-Table -AutoSize
It works fine. However, when I tried to use align for the freespace column to right:
#{Name="Freespace(byte)"; Expression={"{0:N0}" -f $_.FreeSpace}; align="right"}, '
I got error message "Specified method is not supported". Not sure if there is any way to align the value to right?
The align directive goes in a hashtable that is specified to the Format-Table cmdlet. IOW, align is not a supported hashtable entry for Select-Object. So make sure to do your formatting via hashtables in the hashtable passed to Format-Table e.g.:
gps | select name,pm | format-table #{n='Name';e={$_.Name};align='right'},PM
or in your case:
$outData | Format-Table Name,
#{n="Freespace(byte)";e={"{0:N0}" -f $_.FreeSpace};a="right"}
Given the updates to Powershell in the last 8 years, this answer may not have existed in '10.
The trick is to assign a number of columns within the calculated expression's format block {0:N0}, once assigned, it will align the column to the right.
In the original example, include ,15 as part of the number formatting:
#{Name="Freespace(byte)"; Expression={"{0,15:N0}" -f $_.FreeSpace}}
I generally use the character count of the Name= value to ensure the entire name is visible.
here is an ugly looking one liner that builds from several other threads:
Get-WmiObject win32_LogicalDisk | where { $_.DriveType -eq 3 } | Format-Table DeviceID,VolumeName,#{N="Size";E={'{0:N0}' -f $_.Size};a="right"},#{N="FreeSpace";E={'{0:N0}' -f $_.FreeSpace};a="right"},#{N="Used";E={'{0:N0}' -f ($_.Size - $_.FreeSpace)};a="right"}