# Friendly Output: Colors and Progress

Write-Progress shows a progress bar while a long job runs, so nobody wonders whether it's stuck, and $PSStyle colors make the important lines stand out on the screen, as long as the colors stay in what you show and never go into the objects a tool hands back.

Journeyman · PowerShell · Updated November 10, 2026

## After this lesson you can

- Show a progress bar for a long job with Write-Progress, and close it with -Completed
- Color words for the screen with $PSStyle and Write-Host, and keep colors out of the objects a tool hands back

## Key takeaways

- `Write-Progress -Activity -Status -PercentComplete` shows a bar while a job runs, and `-Completed` closes it.
- Progress bars and `Write-Host` lines are for the screen: the pipeline and `>` never get them.
- `$PSStyle.Foreground.Green` starts a color and `$PSStyle.Reset` ends it.
- A color written into a property travels with it: the name no longer matches, and the codes end up in a spreadsheet.
- Objects carry plain values; a Show- command at the end of the line adds the color.

You start an import of three hundred old recipe cards, and the cursor sits there blinking. Is it working? Is it stuck? Should you press Ctrl+C? A minute later it finishes, and prints three hundred lines in one plain color, so the quick weeknight dinners you were looking for are lost in the list. Apps you use every day answer both problems: a progress bar while they work, and color for what matters. Your tool can have both, as long as the color stays on the screen.

This lesson adds two commands to `RecipeBox.ps1` from *Where a Tool Keeps Its Data*. Make a folder of pretend old cards first, each with a name, minutes and a category on three lines:

```powershell
$cards = "$HOME\terminal-practice\recipe-box\old-cards"
New-Item -ItemType Directory -Force -Path $cards | Out-Null
Set-Content "$cards\card-01.txt" -Value 'Black Bean Tacos', 25, 'Dinner'
Set-Content "$cards\card-02.txt" -Value 'Banana Bread', 70, 'Baking'
Set-Content "$cards\card-03.txt" -Value 'Tomato Soup', 35, 'Lunch'
Set-Content "$cards\card-04.txt" -Value 'Pancakes', 20, 'Breakfast'
Set-Content "$cards\card-05.txt" -Value 'Fruit Salad', 15, 'Snack'
```

## Is it stuck? \`Write-Progress\`

A progress bar is the "now boarding, 3 of 5" board at the gate: it says what's happening and how far along it is. `Write-Progress` draws one. `-Activity` names the job, `-Status` says what it's on now, and `-PercentComplete` is how far along it is, from 0 to 100. Add this function to `RecipeBox.ps1`:

```powershell
function Import-RecipeCard {
    param([string]$Path)
    $cards = @(Get-ChildItem $Path -Filter *.txt)
    $done = 0
    foreach ($card in $cards) {
        $percent = $done / $cards.Count * 100
        Write-Progress -Activity 'Importing recipe cards' -Status $card.Name -PercentComplete $percent
        $lines = Get-Content $card.FullName
        Add-Recipe -Name $lines[0] -Minutes $lines[1] -Category $lines[2]
        Start-Sleep -Milliseconds 500   # stands in for a slow job
        $done++
    }
    Write-Progress -Activity 'Importing recipe cards' -Completed
}
```

Five cards take no time at all; three hundred cards, or a folder on a slow network drive, do. `Start-Sleep` pauses half a second per card so you can watch the bar. The last line, with `-Completed`, closes the bar; without it, the bar can stay on the screen until the whole script ends.

Load the file again and import the cards. Each recipe the import adds is a result, so keep them in a variable:

```powershell
. .\RecipeBox.ps1
$imported = Import-RecipeCard .\old-cards
$imported.Count
```

```text
5
```

While it runs, a bar fills across the window with *Importing recipe cards* and the card it's on:

![Windows Terminal during Import-RecipeCard: a progress bar near the top reading Importing recipe cards, with card-03.txt as its status and the bar a little under half full](https://www.ask-codey.com/lessons/TODO)

The bar is for the screen only, like `Write-Host` in *Where Output Goes: `Write-Output`, `Write-Host` and Friends*: the pipeline and `>` never get it, so `$imported` holds the five recipes and nothing else. In Windows PowerShell 5.1 the bar is a larger block across the top of the window; it works the same way.

## Colors for the screen: \`$PSStyle\`

`$PSStyle`, from *A Prompt in Your Colors*, names the codes that switch a terminal's color: `$PSStyle.Foreground.Green` starts green, and `$PSStyle.Reset` (read "reset") goes back to normal. Add a command that shows the box for people, with every recipe of 20 minutes or less in green:

```powershell
function Show-Recipe {
    foreach ($recipe in Get-Recipe) {
        $line = "$($recipe.Name), $($recipe.Minutes) min, $($recipe.Category)"
        if ($recipe.Minutes -le 20) {
            $line = "$($PSStyle.Foreground.Green)$line$($PSStyle.Reset)"
        }
        Write-Host $line
    }
}
```

```powershell
. .\RecipeBox.ps1
Show-Recipe
```

```text
Lemon Garlic Pasta, 30 min, Dinner
Overnight Oats, 10 min, Breakfast
Black Bean Tacos, 25 min, Dinner
Banana Bread, 70 min, Baking
Tomato Soup, 35 min, Lunch
Pancakes, 20 min, Breakfast
Fruit Salad, 15 min, Snack
```

On your screen, the oats, the pancakes and the fruit salad are green. Forget `$PSStyle.Reset`, and every line after the first quick one turns green too. Windows PowerShell 5.1 has no `$PSStyle`; there, write `Write-Host $line -ForegroundColor Green`.

## Never in the objects

It's tempting to color the name inside `Get-Recipe` itself, so the green shows wherever the recipes go. Here's what that does to one recipe:

```powershell
$oats = [PSCustomObject]@{ Name = "$($PSStyle.Foreground.Green)Overnight Oats$($PSStyle.Reset)"; Minutes = 10; Category = 'Breakfast' }
$oats.Name.Length
@($oats | Where-Object Name -eq 'Overnight Oats').Count
```

```text
23
0
```

The color codes are hidden characters, **ANSI escape codes**, and they're part of the name now. *Overnight Oats* is 14 characters, but this name is 23, so a search for it finds nothing. Export it, and the CSV file gets the codes too, so a spreadsheet shows `[32m` and `[0m` around the name.

So keep the two jobs apart. `Get-Recipe` hands back plain objects that sort, filter and save cleanly. `Show-Recipe` is the end of the line: it writes to the screen with `Write-Host` and hands nothing on, so `$shown = Show-Recipe` prints the list and leaves `$shown` empty. Color what people read; never color what commands read.

![Windows Terminal after Show-Recipe: seven recipe lines, with Overnight Oats, 10 min, Breakfast and Pancakes, 20 min, Breakfast and Fruit Salad, 15 min, Snack in green and the other four in the normal color](https://www.ask-codey.com/lessons/TODO)

## Check yourself

2 questions to try on the lesson page.

## So what

A bar that moves and a color that points the way make a tool pleasant to use; keeping them on the screen, out of the objects, keeps it useful to every command that comes after.

## Key terms

- [Write-Progress](https://www.ask-codey.com/glossary/write-progress)
- [$PSStyle](https://www.ask-codey.com/glossary/psstyle)
- [ANSI Escape Code](https://www.ask-codey.com/glossary/ansi-escape-code)
- [Write-Host](https://www.ask-codey.com/glossary/write-host)

## Cheat sheets

- [Friendly Output: Colors and Progress](https://www.ask-codey.com/cheat-sheets/powershell-friendly-output-cheat-sheet)

## Related

- [A Prompt in Your Colors](https://www.ask-codey.com/lessons/powershell-custom-prompt)
- [Where Output Goes: \`Write-Output\`, \`Write-Host\` and Friends](https://www.ask-codey.com/lessons/powershell-output-streams)
- [Where a Tool Keeps Its Data](https://www.ask-codey.com/lessons/powershell-app-data)
- [Objects of Your Own: \`\[PSCustomObject\]\`](https://www.ask-codey.com/lessons/powershell-custom-objects)

Source: https://www.ask-codey.com/lessons/powershell-friendly-output
