Why I Built This
PowerShell’s Get-ChildItem works, but the output isn’t great to look at. I wanted something more like exa or lsd in the Unix world—emoji icons for file types, sizes in KB/MB/GB instead of raw bytes, and timestamps shown as “5d ago” instead of “2025-04-15 14:32:01”. I also wanted to sort directories first and have short aliases for common operations.
Module Architecture
Peek follows a simple, single-file module structure optimized for easy distribution:
pwsh-peek/
├── DirectoryListing.psd1 # Module manifest (metadata & exports)
├── DirectoryListing.psm1 # All functions in one file
├── README.md
└── pwsh-peek/ # Marketing website (Vue 3 + Vite)
└── public/
└── install.ps1 # Web installer script
The module is intentionally flat—no Public/ or Private/ folders. This keeps installation simple (just download two files) while still maintaining clean separation through function scoping.
Core Implementation
Human-Readable Sizes
Instead of showing 1073741824 bytes, Peek converts to friendly units:
function Convert-ToHumanSize {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[long]$Bytes
)
if ($Bytes -lt 1KB) { return "$Bytes B" }
$sizes = @('KB', 'MB', 'GB', 'TB', 'PB')
$val = [double]$Bytes
$i = 0
while ($val -ge 1024 -and $i -lt $sizes.Count) {
$val = $val / 1024
$i++
}
'{0:N1} {1}' -f $val, $sizes[$i - 1]
}
Relative Timestamps
Dates like “2025-04-15 14:32:01” become “5d ago”:
function Convert-ToRelativeTime {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[datetime]$DateTime
)
$span = (Get-Date) - $DateTime
if ($span.TotalSeconds -lt 60) { return "{0:N0}s ago" -f $span.TotalSeconds }
if ($span.TotalMinutes -lt 60) { return "{0:N0}m ago" -f $span.TotalMinutes }
if ($span.TotalHours -lt 24) { return "{0:N0}h ago" -f $span.TotalHours }
if ($span.TotalDays -lt 7) { return "{0:N0}d ago" -f $span.TotalDays }
if ($span.TotalDays -lt 30) { return "{0:N0}w ago" -f ($span.TotalDays / 7) }
if ($span.TotalDays -lt 365) { return "{0:N0}mo ago" -f ($span.TotalDays / 30) }
return "{0:N0}y ago" -f ($span.TotalDays / 365)
}
The Main Command: Get-DirectoryView
The core Get-DirectoryView function wraps Get-ChildItem and emits custom PSObjects:
function Get-DirectoryView {
[CmdletBinding()]
param(
[string]$Path = '.',
[int]$Depth = 1,
[switch]$Recurse,
[switch]$All,
[switch]$Long,
[switch]$DirsFirst,
[switch]$SortNewest,
[switch]$SortSize,
[switch]$OnlyFiles,
[switch]$OnlyDirs,
[switch]$NoIcons,
[switch]$Raw
)
# ... builds Get-ChildItem params, processes items, outputs formatted table
}
Each item is transformed into a consistent shape:
[PSCustomObject]@{
Icon = $icon # 📁, 📄, or 🔗 (or D/F/L in ASCII mode)
Name = $it.Name
Type = $type # Dir, File, or Link
Size = $size # Human-readable or '-' for directories
Modified = $when # Relative timestamp
Mode = $it.Mode # Only in -Long mode
FullName = $it.FullName
}
Smart Sorting with Directory-First
The -DirsFirst flag uses compound Sort-Object expressions to group directories before files while preserving secondary sort order:
if ($DirsFirst) {
$items = $items | Sort-Object @{
Expression = { $_.PSIsContainer -eq $false }
Descending = $true
}, @{
Expression = { $_.LastWriteTime }
Descending = $true
}, Name
}
Convenience Wrappers & Aliases
Rather than typing peek -All -DirsFirst -SortNewest every time, the module provides wrapper functions with memorable aliases:
| Command | Alias | Short | Description |
|---|---|---|---|
Get-DirectoryView | peek | — | Base listing |
Get-PeekAll | peek-all | pka | Include hidden/system items |
Get-PeekAllRecurse | peek-all-recurse | pkar | Recurse with depth |
Get-PeekFiles | peek-files | pkf | Files only |
Get-PeekDirs | peek-dirs | pkd | Directories only |
Get-PeekAllSize | peek-all-size | pkas | All items, largest first |
Get-PeekAllNewest | peek-all-newest | pkan | All items, newest first |
Handling Terminals Without Emoji Support
Not every terminal can display emoji. Peek has a few ways to handle this: you can pass -NoIcons (or -Ascii) on the command line, set an environment variable $env:PEEK_NO_ICONS = '1', or use Set-NoIconsForPeek -Scope User to save it to a config file. The module checks these in order: command-line first, then environment, then config file, then falls back to showing icons by default.
function Get-PeekPreference {
$noIcons = $false
$source = 'Default'
if ($env:PEEK_NO_ICONS) {
$noIcons = ($env:PEEK_NO_ICONS -ne '0')
$source = 'Env'
}
else {
$cfgPath = Get-PeekConfigPath
if (Test-Path $cfgPath) {
$json = Get-Content $cfgPath -Raw | ConvertFrom-Json
if ($null -ne $json.NoIcons) {
$noIcons = [bool]$json.NoIcons
$source = 'Config'
}
}
}
[PSCustomObject]@{ NoIcons = $noIcons; Source = $source }
}
In ASCII mode, icons become simple letters: D for directory, F for file, L for link.
Installation
The module can be installed with a one-liner:
iex (irm peek.codywilliamson.com/install.ps1)
The installer downloads the module files from GitHub and puts them in your PowerShell modules folder. It supports -Force to overwrite existing installations and -AddToProfile to auto-import on shell startup.
Module Manifest
The .psd1 manifest declares all exports explicitly:
@{
RootModule = '.\DirectoryListing.psm1'
ModuleVersion = '1.0.0'
PowerShellVersion = '7.0'
FunctionsToExport = @(
'Get-DirectoryView',
'Get-PeekAll',
'Get-PeekAllRecurse',
'Get-PeekFiles',
'Get-PeekDirs',
'Get-PeekAllSize',
'Get-PeekAllNewest',
'Get-PeekPreference',
'Set-NoIconsForPeek'
)
AliasesToExport = @(
'peek', 'peek-all', 'peek-all-recurse', 'peek-files',
'peek-dirs', 'peek-all-size', 'peek-all-newest',
'pka', 'pkar', 'pkf', 'pkd', 'pkas', 'pkan'
)
}
What I Learned
A few things stood out while building this. Keeping the module as a single .psm1 file made distribution much simpler—you just download two files and you’re done. Short aliases like pka and pkf make the tool feel more natural to use once you’ve memorized them. And making the output consistent (always the same PSObject shape) means the results are predictable and work well with piping.
I use Peek daily now. It’s part of how I navigate projects.
Links:
- Website: peek.codywilliamson.com
- GitHub: codywilliamson/pwsh-peek
- PowerShell Gallery: DirectoryListing
Enjoyed this article? Share it with others!