1

Table of random numbers

Let's setup a table of numbers:

$items = foreach ($i in 0..10)
{
    [PSCustomObject]@{
        a = Get-Random -Minimum -10.0 -Maximum 10.0
        b = Get-Random -Minimum -10.0 -Maximum 10.0
    }
}

If we display them:

$items | Format-Table

it'll look something like this:

PS C:\Users\dharm> $items | Format-Table

    a     b
    -     -
-7.26  6.45
 3.22 -3.66
-8.41 -7.65
 8.93  1.02
-5.92 -9.02
-8.06 -4.86
 4.86 -1.57
-6.85 -7.35
 3.97 -4.80
 3.39  7.82
 5.01  9.75

Conditional formatting

I'd like to display the positive numbers in green and the negative numbers in red.

Here's a color-number function that uses PSStyle:

function color-number ($val)
{
    if ($val -gt 0) { 
        $PSStyle.Foreground.Green + ('{0,5:N2}' -f $val) + $PSStyle.Reset 
    } 
    elseif ($val -lt 0) {
        $PSStyle.Foreground.Red + ('{0,5:N2}' -f $val) + $PSStyle.Reset
    }
    else { '{0,5:N2}' -f $val }
}

So now if I display the table:

$items | Format-Table @{ L = 'a'; E = { color-number $_.a } }, @{ L = 'b'; E = { color-number $_.b } }

it looks like this:

enter image description here

Question

Is this the recommended way to perform conditional formatting of items in a table?

Or is there a better more idiomatic approach?

I'm using PowerShell 7.3.6.

dharmatech
  • 8,979
  • 8
  • 42
  • 88
  • 1
    This question is either a duplicate with [How to colorise PowerShell output of Format-Table](https://stackoverflow.com/q/20705102/1701026) or **Opinion-based**: This question is likely to be answered with opinions rather than facts and citations. It should be updated so it will lead to fact-based answers. – iRon Aug 13 '23 at 06:26

2 Answers2

2

See my other answer for the best way to achieve this.

Keeping this answer, as this show a simplified way of doing it with a PSCustomObject without having to delve into the depths of creating a ps1xml custom format file.


Actually, you don't even need to format to a table.

You can set your ANSI formatting ($PSStyle) at the item generation or right after, like this:

$items | % { $_.a = color-number $_.a; $_.b = color-number $_.b; $_}

# Format-Table not needed and therefore the output is still [PSCustomObject]
$items

That way, you get the coloring right from the start without loosing the object type in the process. You can print it as is or use Format-Table / Format-List without any expression since the ANSI style is embedded in the values.

Additional notes

A very important detail pointed out by mclayton, while you do not loose the PSCustomObject, the values within the object get converted to string anyhow by applying the formatting so this is bad for the integrity of your data type if you need to do anything else with it.

Sage Pourpre
  • 9,932
  • 3
  • 27
  • 39
  • 2
    The difference is this solution *overwrites* the numeric values in the original data with formatted ```$PSStyle``` strings rather than just using calculated column values in the ```Format-Table``` output - if you subsequently use the ```$items``` data for other things (e.g. ```Export-Csv``` or any line-of-business processing) you're going to get funny results because the properties are now strings with embedded ```$PSStyle``` formatting characters rather than numbers. (Might not be a problem in the OP's particular situation, but worth noting in the general case). – mclayton Aug 12 '23 at 19:11
2

You can achieve this by utilizing a custom Format data ps1xml file. This method allows you to define specific formatting rules for your output, including color.

Here's an example of how you can do this:

First, create a collection of custom objects and assign them a custom type name:


$items = foreach ($i in 0..10) {
    $obj = [PSCustomObject]@{
        a = Get-Random -Minimum -10.0 -Maximum 10.0
        b = Get-Random -Minimum -10.0 -Maximum 10.0
    }
    # Each item of the collection need to have the custom type
    $obj.PSObject.TypeNames.Insert(0, 'CuteData')
    $obj
}
# We had the formatting rules for the object
Update-FormatData -PrependPath 'C:\Github\_Ideas\CuteData.Format.ps1xml' 

The ps1xml file will contain the formatting rules you want to apply, including color specifications.

The best thing is that that rule is really a formatting rule and do not modify the actual object data.

This is what Powershell use internally to provide you with custom views.

References

Here is an example of A ps1xml file tailored to that custom object.

CuteData.Format.ps1xml

<Configuration>
  <ViewDefinitions>
    <View>
      <Name>Default</Name>
      <ViewSelectedBy>
        <TypeName>CuteData</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <TableHeaders>
          <TableColumnHeader>
            <Label>a</Label>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>b</Label>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <ScriptBlock>
                    $Green = { Param($a) $PsStyle.Foreground.Green +  ('{0,5:N2}' -f $a) + $PsStyle.Reset } 
                    $Red = { Param($a) $PsStyle.Foreground.Red +  ('{0,5:N2}' -f $a) + $PsStyle.Reset }

                    if ($_.a -gt 0) {
                        . $Green $($_.a)
                    }
                    elseif ($_.a -lt 0) {
                        . $Red $_.a
                    }
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
               <ScriptBlock>
                    $Green = { Param($a) $PsStyle.Foreground.Green +  ('{0,5:N2}' -f $a) + $PsStyle.Reset } 
                    $Red = { Param($a) $PsStyle.Foreground.Red +  ('{0,5:N2}' -f $a) + $PsStyle.Reset }

                    if ($_.b -gt 0) {
                        . $Green $($_.b)
                    }
                    elseif ($_.b -lt 0) {
                        . $Red $_.b
                    }
                </ScriptBlock>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

Sage Pourpre
  • 9,932
  • 3
  • 27
  • 39