Powershell - Prefix each line of Format-Table with String - powershell

I would like to know if there is an easy way of prefixing each line of a powershell table with a String.
For example, if I create an Array using the following code:
$Array = #()
$Object = #{}
$Object.STR_PARAM = "A"
$Object.INT_PARAM = 1
$Array += [PSCustomObject] $Object
$Object = #{}
$Object.STR_PARAM = "B"
$Object.INT_PARAM = 2
$Array += [PSCustomObject] $Object
Calling Format-Table give the following output:
$Array | Format-Table -AutoSize
STR_PARAM INT_PARAM
--------- ---------
A 1
B 2
Instead, I would like to have the following:
$Array | Format-Table-Custom -AutoSize -PrefixString " "
STR_PARAM INT_PARAM
--------- ---------
A 1
B 2
And if possible, I would also like to be able to use the Property parameter like this:
$SimpleFormat = #{Expression={$_.STR_PARAM}; Label="String Param"},
#{Expression={$_.INT_PARAM}; Label="Integer Param"};
$Array | Format-Table-Custom -Property $SimpleFormat -AutoSize -PrefixString "++"
++String Param Integer Param
++------------ -------------
++A 1
++B 2
Any help would be appreciated. Thanks.

You could just use the format expressions directly:
$f = #{Expression={"++" + $_.STR_PARAM}; Label="++String Param"},
#{Expression={$_.INT_PARAM}; Label="Integer Param"};
$Array | Format-Table $f -AutoSize
Output
++String Param Integer Param
-------------- -------------
++A 1
++B 2
Update to use expression and filter
Filter Format-Table-Custom
{
Param
(
[string]
$PrefixString,
[object]
$Property
)
end {
$rows = $input | Format-Table $property -AutoSize | Out-String
$lines = $rows.Split("`n")
foreach ($line in $lines) {
if ($line.Trim().Length -gt 0) {
$PrefixString + $line
}
}
}
}
$f = #{Expression={"--" + $_.STR_PARAM}; Label="--String Param"},
#{Expression={$_.INT_PARAM}; Label="Integer Param"};
$Array | Format-Table-Custom -Property $f -PrefixString "++"
Output
++--String Param Integer Param
++-------------- -------------
++--A 1
++--B 2

Related

Powershell object where the expanded properties become just properties

I collect two files that both contain an equal number of strings and am trying to join them into 1 object with each being a named propery.
$Users = Get-content C:\temp\Users.txt
$Languages = Get-content C:\temp\Languages.txt
Using the code below:
$myHashtable = #{
Name = $user
Language = $Languages
}
$myObject = [pscustomobject]$myHashtable
The $myObject looks like
Name Language
--------- -------------
{Todd, Sara, Mike...} {English, Spanish, French...}
Can I adjust my code in so $MyObject outputs the following?
Name Language
--------- -------------
Todd English
Sara Spanish
Mike French
Based on your additional information, you could use:
$Users = Get-content C:\temp\Users.txt
$Languages = Get-content C:\temp\Languages.txt
$MyObjects = $Users | ForEach-Object -Begin {$i=0} -Process {
[PSCustomObject]#{
'Name' = $Users[$i]
'Language' = $Languages[$i++]
}
} -End {}
This is fine for working with the whole collecdtion. If you wanted random access to individual elements, a hashtable keyed on 'Name' might be a better idea:
$Users = Get-content C:\temp\Users.txt
$Languages = Get-content C:\temp\Languages.txt
$MyObjectHashTable = $Users | ForEach-Object -Begin { $hash=#{} ; $i=0 } -Process {
$hash.Add( $Users[$i] , $Languages[$i++] )
} -End { $hash }

How to use Group-Object on this?

I am trying to get all the accounts from $f which do not match the accounts in $table4 into $accounts. But I need to also check if the occupancy number matches or not.
CSV $f:
Account_no |occupant_code
-----------|------------
12345 | 1
67890 | 2
45678 | 3
DataTable $table4
Account_no |occupant_code
-----------|------------
12345 | 1
67890 | 1
45678 | 3
Current code:
$accounts = Import-Csv $f |
select account_no, occupant_code |
where { $table4.account_no -notcontains $_.account_no }
What this needs to do is to check that occupant_code doesn't match, i.e.:
12345: account and occupant from $f and $table4 match; so it's ignored
67890: account matches $table4, but occupancy_code does not match, so it is added to $accounts.
Current result:
Desired result: 67890
I believe I need to use Group-Object, but I do not know how to use that correctly.
I tried:
Import-Csv $f |
select account_no, occupant_code |
Group-Object account_no |
Where-Object { $_.Group.occupant_code -notcontains $table4.occupant_code }
An alternative to Bill's suggestion would be to fill a hashtable with your reference data ($table4) and look up the occupant_code value for each account from $f, assuming that your account numbers are unique:
$ref = #{}
$table4 | ForEach-Object {
$ref[$_.Account_no] = $_.occupant_code
}
$accounts = Import-Csv $f |
Where-Object { $_.occupant_code -ne $ref[$_.Account_no] } |
Select-Object -Expand Account_no
Compare-Object?
csv1.csv:
Account_no,occupant_code
12345,1
67890,2
45678,3
csv2.csv:
Account_no,occupant_code
12345,1
67890,1
45678,3
PowerShell command:
Compare-Object (Import-Csv .\csv1.csv) (Import-Csv .\csv2.csv) -Property occupant_code -PassThru
Output:
Account_no occupant_code SideIndicator
---------- ------------- -------------
67890 1 =>
67890 2 <=
$f | InnerJoin $table4 {$Left.Account_no -eq $Right.Account_no -and $Left.occupant_code -ne $Right.occupant_code} #{Account_no = {$Left.$_}} | Format-Table
Result:
occupant_code Account_no
------------- ----------
{2, 1} 67890
For details see: In Powershell, what's the best way to join two tables into one?
In addition to all the other answers, you might be able to leverage the IndexOf() method on arrays
$services = get-service
$services.name.IndexOf("xbgm")
240
I am on a tablet right now and don't have a handy way to test it, but something along these lines might work for you:
$table4.account_no.IndexOf($_.account_no)
should fetch the index your account_no lives in for $table 4, so you could jam it all into one ugly pipe:
$accounts = Import-Csv $f | select account_no, occupant_code |
where { ($table4.account_no -notcontains $_.account_no) -or ($table4[$table4.account_no.IndexOf($_.account_no)].occupant_code -ne $_.occupant_code) }
An inner join or a normal loop might just be cleaner though, especially if you want to add some other stuff in. Since someone posted an innerjoin, you could try a loop like:
$accounts = new-object System.Collections.ArrayList
$testSet = $table4.account_no
foreach($myThing in Import-Csv $f)
{
if($myThing.account_no -in $testSet )
{
$i = $testSet.IndexOf($myThing.account_no)
if($table4[$i].occupant_code -eq $myThing.occupant_code) {continue}
}
$accounts.add($myThing)
}
Edit for OP, he mentioned $table4 is a data.table
There is probably a much better way to do this, as I haven't used data.table before, but this seems to work fine:
$table = New-Object system.Data.DataTable
$col1 = New-Object system.Data.DataColumn Account_no,([string])
$col2 = New-Object system.Data.DataColumn occupant_code,([int])
$table.columns.add($col1)
$table.columns.add($col2)
$row = $table.NewRow()
$row.Account_no = "12345"
$row.occupant_code = 1
$table.Rows.Add($row)
$row = $table.NewRow()
$row.Account_no = "67890"
$row.occupant_code = 1
$table.Rows.Add($row)
$row = $table.NewRow()
$row.Account_no = "45678"
$row.occupant_code = 3
$table.Rows.Add($row)
$testList = #()
$testlist += [pscustomobject]#{Account_no = "12345"; occupant_code = 1}
$testlist += [pscustomobject]#{Account_no = "67890"; occupant_code = 2}
$testlist += [pscustomobject]#{Account_no = "45678"; occupant_code = 3}
$accounts = new-object System.Collections.ArrayList
$testSet = $table.account_no
foreach($myThing in $testList)
{
if($myThing.account_no -in $testSet )
{
$i = $testSet.IndexOf($myThing.account_no)
if($table.Rows[$i].occupant_code -eq $myThing.occupant_code) {continue}
}
$accounts.add($myThing) | out-null
}
$accounts

Powershell: Filter Hashtable - and get back a Hastable

Filtering a Hashtable using GetEnumerator always returns a object[] instead of a Hashtable:
# Init Hashtable
$items = #{ a1 = 1; a2 = 2; b1 = 3; b2 = 4}
# apply a filter
$filtered = $items.GetEnumerator() | ?{ $_.Key -match "a.*" }
# The result looks great
$filtered
Name Value
---- -----
a2 2
a1 1
# … but it is not a Hashtable :-(
$filtered.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Is there a nice solution to this problem?
Thanks a lot for any Help!,
kind regards,
Tom
$filtered is an array of dictionary entries. There's no single cast or ctor for this as far as I know.
You can construct a hash though:
$hash = #{}
$filtered | ForEach-Object { $hash.Add($_.Key, $_.Value) }
Another workflow:
# Init Hashtable
$items = #{ a1 = 1; a2 = 2; b1 = 3; b2 = 4}
# Copy keys to an array to avoid enumerating them directly on the hashtable
$keys = #($items.Keys)
# Remove elements not matching the expected pattern
$keys | ForEach-Object {
if ($_ -notmatch "a.*") {
$items.Remove($_)
}
}
# $items is filtered
Here's an even simpler function, it even has include and exclude functionality
function Select-HashTable {
[CmdletBinding()]
param (
[Parameter(Mandatory,ValueFromPipeline)][Hashtable]$Hashtable,
[String[]]$Include = ($HashTable.Keys),
[String[]]$Exclude
)
if (-not $Include) {$Include = $HashTable.Keys}
$filteredHashTable = #{}
$HashTable.keys.where{
$PSItem -in $Include
}.where{
$PSItem -notin $Exclude
}.foreach{
$filteredHashTable[$PSItem] = $HashTable[$PSItem]
}
return $FilteredHashTable
}
Examples:
$testHashtable = #{a=1;b=2;c=3;d=4}
$testHashTable | Select-HashTable -Include a
Name Value
---- -----
a 1
$testHashTable | Select-HashTable -Exclude b
Name Value
---- -----
c 3
d 4
a 1
$testHashTable | Select-HashTable -Include a,b,c -Exclude b
Name Value
---- -----
a 1
c 3
As the accepted answer was resulting in a BadEnumeration exception for me (but still worked), I modified it to not throw an exception and also made sure that the original HashTable is not modified by cloning it first:
# Init Hashtable
$items = #{ a1 = 1; a2 = 2; b1 = 3; b2 = 4}
$filtered = $items.Clone()
$items.Keys | ForEach-Object {
if ($_ -notmatch "a.*") {
$filtered.Remove($_)
}
}
On a modern PowerShell (5+ as far as I remember) you can use reduce pattern. For that you need to use this form of ForEach-Object:
$Hashtable.Keys | ForEach-Object {$FilteredHashtable = #{}} {
if ($_ -eq 'Example') {
$FilteredHashtable[$_] = $Hashtable[$_];
}
} {$FilteredHashtable}
Yes, this snippet will return Hashtable.

Iterate through an array of powershell custom objects and output to html

I have an array :
$results =#()
Then i loop with custom logic through wmi and create custom objects that i add to the array like this:
$item= #{}
$item.freePercent = $freePercent
$item.freeGB = $freeGB
$item.system = $system
$item.disk = $disk
$results += $item
I know want to to some stuff on the results array, like converting to html .
I can do it with a foreach and custom html writing but i want to use convertto-html...
P.S. I can print out data like this but only this:.
foreach($result in $results) {
$result.freeGB
}
Custom object creation doesn't work like you seem to think. The code
$item= #{}
$item.freePercent = $freePercent
$item.freeGB = $freeGB
$item.system = $system
$item.disk = $disk
creates a hashtable, not a custom object, so you're building a list of hashtables.
Demonstration:
PS C:\> $results = #()
PS C:\> 1..3 | % {
>> $item = #{}
>> $item.A = $_ + 2
>> $item.B = $_ - 5
>> $results += $item
>> }
>>
PS C:\> $results
Name Value
---- -----
A 3
B -4
A 4
B -3
A 5
B -2
PS C:\> $results[0]
Name Value
---- -----
A 3
B -4
Change your object creation to this:
$item = New-Object -Type PSCustomObject -Property #{
'freePercent' = $freePercent
'freeGB' = $freeGB
'system' = $system
'disk' = $disk
}
$results += $item
so you get the desired list of objects:
PS C:\> $results = #()
PS C:\> 1..3 | % {
>> $item = New-Object -Type PSCustomObject -Property #{
>> 'A' = $_ + 2
>> 'B' = $_ - 5
>> v}
>> $results += $item
>> }
>>
PS C:\> $results
A B
- -
3 -4
4 -3
5 -2
PS C:\> $results[0]
A B
- -
3 -4
Also, appending to an array in a loop is bound to perform poorly. It's better to just "echo" the objects inside the loop and assign the result to the list variable:
$results = foreach (...) {
New-Object -Type PSCustomObject -Property #{
'freePercent' = $freePercent
'freeGB' = $freeGB
'system' = $system
'disk' = $disk
}
}
Pipe $results into ConvertTo-Html to convert the list to an HTML page (use the parameter -Fragment if you want to create just an HTML table instead of an entire HTML page).
$results | ConvertTo-Html
An even better approach would be to pipeline your whole processing like this:
... | ForEach-Object {
New-Object -Type PSCustomObject -Property #{
'freePercent' = $freePercent
'freeGB' = $freeGB
'system' = $system
'disk' = $disk
}
} | ConvertTo-Html
You aren't creating a custom object, you're creating a hash table.
Assuming you've got at least V3:
[PSCustomObject]#{
freePercent = $freePercent
freeGB = $freeGB
system = $system
disk = $disk
}

Convert the result of select to array

I have the following code.
$l = #("A", "B", "X", "Y")
echo "A,B,X`n1,2,3,4" > .\myFile # Create test file
$f = cat myFile | ConvertFrom-Csv | gm -MemberType NoteProperty | select Name
compare $l $f
$a = .... # convert $f to array
compare $l $a
How to convert the $f to array so it can be compared with an array? Bracing #(...) doesn't work.
I got the following result when compare $l and $f.
PS C:\Users\nick> compare $l $f
InputObject SideIndicator
----------- -------------
#{Name=A} =>
#{Name=B} =>
#{Name=X} =>
A <=
B <=
X <=
Y <=
Replace select Name with select -Expand Name or ForEach-Object { $_.Name }.
Another approach, if you are looking to get an array from a single property would be to use the "ExpandProperty" switch from Select like this:
$f = cat myFile | ConvertFrom-Csv | gm -MemberType NoteProperty | Select -ExpandProperty Name
You can cast both objects to ArrayLists then compare them.
[System.Collections.ArrayList]$array1 = $l
[System.Collections.ArrayList]$array2 = $f
Try this:
$a = New-Object System.Collections.ArrayList;
$f | % { $null = $a.Add($_.Name); }
compare $l $a;