Deterministic Randomness in PowerShell: Seed Once, Reproduce Always
Randomness is great for simulations, tests, sampling, shuffling, and demos—but debugging flaky tests or explaining a surprise shuffle to a teammate is not. The fix is simple: make randomness intentional. In PowerShell, you can seed a random number generator once per run, reuse it everywhere, and log the seed so anyone can reproduce results. This post shows practical patterns and recipes to make your randomness deterministic and dependable.
Why Deterministic Randomness Matters
- Reproducible tests: When a test fails intermittently, rerun with the same seed to reproduce the exact sequence of random values.
- Stable shuffles: Demos and data presentations behave predictably while still “looking random.”
- Easier debugging: You can binary-search issues across runs knowing the random inputs are identical.
- CI/CD friendly: Log and propagate a seed across jobs so the same run can be replayed locally.
Core Pattern: Seed Once, Reuse Everywhere, Log the Seed
The key is to create a single [System.Random] instance at the start of your run, then pass it to anything that needs randomness. When you use cmdlets like Get-Random, you can supply the RNG to maintain a single, consistent sequence of values throughout the run. Finally, record the seed so others can reproduce your exact results.
Quick Start Example
$seed = 424242
$rng = [System.Random]::new($seed)
$names = @('alpha','beta','gamma','delta','epsilon')
# Deterministic shuffle
$shuffled = $names | Sort-Object { $rng.Next() }
# Deterministic sampling (3 items)
$samples = 1..3 | ForEach-Object { $names[$rng.Next(0, $names.Count)] }
Write-Host ('Shuffled: {0}' -f ($shuffled -join ', '))
Write-Host ('Samples: {0}' -f ($samples -join ', '))What you get: reproducible tests, stable shuffles, easier debugging, and predictable demos—every time.
Working with Get-Random
Get-Random provides convenient random selection and numeric ranges. For deterministic behavior across multiple calls, inject your own [System.Random] instance.
Pick from a list deterministically
$seed = 424242
$rng = [System.Random]::new($seed)
$names = @('alpha','beta','gamma','delta','epsilon')
# Deterministic 3 picks (with replacement)
$pick = Get-Random -InputObject $names -Count 3 -Random $rng
$pick -join ', 'Deterministic numeric ranges
$seed = 424242
$rng = [System.Random]::new($seed)
# 5 pseudo-random integers from 1000..9999, reproducible
1..5 | ForEach-Object { Get-Random -Minimum 1000 -Maximum 10000 -Random $rng }About -SetSeed vs a shared [System.Random]
- -SetSeed: Good for one-off deterministic calls, but the sequence restarts each time you use it. It won’t synchronize with randomness elsewhere in your script.
- Shared [System.Random]: Best for whole-run control. You can pass the same RNG instance to
Get-Randomand also use$rng.Next()in custom logic, keeping one consistent sequence.
# One-off deterministic call using -SetSeed
Get-Random -InputObject $names -Count 2 -SetSeed 424242
# Whole-run consistency using a shared RNG
$rng = [System.Random]::new(424242)
Get-Random -InputObject $names -Count 2 -Random $rng
# Later: reuse $rng again for further picks or numeric rangesPractical Recipes
1) Deterministic Shuffle
Shuffling by assigning a random key per element is straightforward and repeatable with a seeded RNG.
$rng = [System.Random]::new(424242)
$shuffled = $names | Sort-Object { $rng.Next() }
$shuffledThis method is unbiased for practical purposes because keys are drawn uniformly from a huge integer space and collisions are vanishingly rare.
2) Deterministic Sampling without replacement
For a random subset with no duplicates, shuffle then take the first N items.
$rng = [System.Random]::new(424242)
$subset = ($names | Sort-Object { $rng.Next() })[0..2]
$subset3) Deterministic Sampling with replacement
$rng = [System.Random]::new(424242)
$samples = 1..3 | ForEach-Object { $names[$rng.Next(0, $names.Count)] }
$samples4) Deterministic weighted choice
When items have weights, transform to cumulative weights and draw via a single random threshold.
$rng = [System.Random]::new(424242)
$items = @(
@{ Name = 'alpha'; Weight = 1 }
@{ Name = 'beta'; Weight = 2 }
@{ Name = 'gamma'; Weight = 3 }
)
$total = ($items | Measure-Object -Property Weight -Sum).Sum
$threshold = $rng.NextDouble() * $total
$acc = 0
foreach ($it in $items) {
$acc += $it.Weight
if ($acc -ge $threshold) { $it.Name; break }
}5) Deterministic GUID-like IDs (not for security)
For demo IDs that “look random” but are reproducible, derive bytes from the RNG. Do not use this for security or uniqueness guarantees beyond your demo scope.
$rng = [System.Random]::new(424242)
$bytes = New-Object byte[] 16
$rng.NextBytes($bytes)
[Guid]::new($bytes)Seeding and Logging for Reproducibility
Make it easy to rerun with the same seed. Accept a -Seed parameter, fall back to $env:RNG_SEED, and if neither is provided, generate one, then log it.
param(
[int]$Seed
)
$seed = if ($PSBoundParameters.ContainsKey('Seed')) { $Seed }
elseif ($env:RNG_SEED) { [int]$env:RNG_SEED }
else { [int]((Get-Date).ToFileTimeUtc() -band 0x7fffffff) }
$rng = [System.Random]::new($seed)
# Log the seed so teammates can reproduce
$log = @{ timestamp = (Get-Date).ToString('o'); seed = $seed; script = $PSCommandPath }
$log | ConvertTo-Json | Tee-Object -FilePath './run-seed.log.json' -Append | Out-HostTo reproduce:
- Windows CMD:
set RNG_SEED=424242 & pwsh -File .\script.ps1 - PowerShell:
$env:RNG_SEED = 424242; pwsh -File .\script.ps1 - Linux/macOS:
RNG_SEED=424242 pwsh -File ./script.ps1
Testing and CI/CD
Pester tests
Initialize a shared RNG in BeforeAll and reuse it in your tests.
BeforeAll {
$script:Seed = 424242
$script:Rng = [System.Random]::new($script:Seed)
}
It 'selects 3 deterministic names' {
$names = 'alpha','beta','gamma','delta','epsilon'
$pick = Get-Random -InputObject $names -Count 3 -Random $script:Rng
$pick | Should -Be @('beta','gamma','delta') # Example expected
}CI pipelines
- Pin a seed for nightly runs to stabilize flaky tests while you diagnose.
- Log the seed from every job to an artifact so failures can be replayed locally.
- Rotate seeds (e.g., daily) to widen input coverage while staying reproducible within a given day.
# GitHub Actions example snippet
- name: Run tests
env:
RNG_SEED: 424242
run: pwsh -File ./build/test.ps1Parallelism and Runspaces
[System.Random] isn’t thread-safe. If you use parallelism (ForEach-Object -Parallel, runspaces, or jobs), create one RNG per worker. For determinism, derive each worker’s seed from a master seed and a stable index, not from thread IDs or PIDs (which can vary between runs).
$masterSeed = 424242
$items = 0..9
$items | ForEach-Object -Parallel {
param($i, $seed)
$workerSeed = [System.HashCode]::Combine($seed, $i)
$rng = [System.Random]::new($workerSeed)
# Use $rng deterministically for this item
[pscustomobject]@{ Index = $i; Value = $rng.Next(0,100) }
} -ArgumentList ($_, $masterSeed)If result ordering matters, consider avoiding parallelism or sorting the results by a deterministic key after the run.
Tips, Pitfalls, and Security
- Reuse the RNG: Don’t create new
[System.Random]instances repeatedly; it resets the sequence and can cluster when constructed in quick succession. - Boundaries matter:
$rng.Next(min,max)is inclusive ofminand exclusive ofmax; plan ranges accordingly. - Don’t use System.Random for crypto: It’s not cryptographically secure. For secrets or tokens, use
[System.Security.Cryptography.RandomNumberGenerator]—and don’t try to make that deterministic. - Record context: Along with the seed, log the PowerShell version and OS when reproducibility really matters.
Copy-Paste: Deterministic PowerShell Snippet
$seed = 424242
$rng = [System.Random]::new($seed)
$names = @('alpha','beta','gamma','delta','epsilon')
# Deterministic shuffle
$shuffled = $names | Sort-Object { $rng.Next() }
# Deterministic sampling (3 items)
$samples = 1..3 | ForEach-Object { $names[$rng.Next(0, $names.Count)] }
Write-Host ('Shuffled: {0}' -f ($shuffled -join ', '))
Write-Host ('Samples: {0}' -f ($samples -join ', '))Make randomness intentional in PowerShell: seed once, reuse your RNG, and log the seed. That simple discipline buys you reproducible tests, stable shuffles, predictable demos, and stress-free debugging.
Further reading: PowerShell Advanced Cookbook → https://www.amazon.com/PowerShell-Advanced-Cookbook-scripting-advanced-ebook/dp/B0D5CPP2CQ/