Deterministic Random Data in PowerShell: Seed Once, Reproduce Always
Random data is incredibly useful for demos, tests, and data generation, but non-deterministic randomness makes failures hard to reproduce and results hard to compare. The fix is simple and powerful: seed your random source once, use a single Random instance, and log the seed so you can replay the exact same sequence later. In this post, youll learn how to make random in PowerShell deterministic by default, with patterns you can drop into scripts, tests, and CI pipelines.
Why Deterministic Randomness Matters
When you need data that looks random but behaves predictably every run, determinism is your friend. Seeding allows you to generate stable sequences that you can replay on demand. This is invaluable for:
- Testing: Failing tests become reproducible; flaky randomness stops hiding defects.
- Data generation: Stable fixtures help you compare performance and validate outputs across builds.
- Demos and tutorials: Live examples behave the same every time you present them.
- Debugging: You can capture and reuse the exact seed that triggered a bug.
The core rule: seed once, reuse the same Random instance, and log the seed.
Implementing Deterministic Random in PowerShell
Heres a minimal, practical pattern you can drop into any script. It uses a single [System.Random] instance seeded once, generates numbers, and produces a deterministic shuffle. The seed is parameterized for reproducibility.
param([int]$Seed = 1337, [int]$Count = 5)
$rand = [System.Random]::new($Seed)
$nums = 1..$Count | ForEach-Object { $rand.Next(100,999) }
$names = @('alpha','beta','gamma','delta','epsilon')
$shuffled = $names | Sort-Object { $rand.Next() }
[pscustomobject]@{
Seed = $Seed
Numbers = $nums -join ','
Order = $shuffled -join ','
}
Run it twice with the same seed and youll get the same numbers and the same order every time. Swap in a different seed and you get a different, but still reproducible, sequence.
Best Practices in This Pattern
- Seed once: Call
[System.Random]::new($Seed)one time and reuse that instance. Creating multiple instances with the same seed (or within the same clock tick) can produce identical sequences you didnt intend. - Use a single instance: Pass the same
$randthrough your functions or enclose it in a module-level scope so all random operations draw from the same deterministic stream. - Log the seed: Always print or persist the seed used. Its your key to reproducing results.
Logging the Seed for Reproducibility
Make the seed obvious in your script output and store it with build artifacts or test logs.
param([int]$Seed = (Get-Random -Minimum 1 -Maximum [int]::MaxValue))
$rand = [System.Random]::new($Seed)
Write-Information -MessageData "RunSeed=$Seed" -InformationAction Continue
# Optionally persist to a file
Set-Content -Path ./.seed -Value $Seed
When a test fails, grab the seed from logs and rerun with -Seed <value>.
Advanced Patterns: Shuffles, Bytes, Parallelism, Tests
1) Stable, Unbiased Shuffling (FisherYates)
Sorting by $rand.Next() is convenient and deterministic, but its not a perfectly unbiased shuffle. For better uniformity, implement FisherYates with the same seeded Random:
function Invoke-FisherYatesShuffle {
param(
[Parameter(Mandatory)] [object[]]$InputObject,
[Parameter(Mandatory)] [System.Random]$Random
)
$arr = [object[]]::new($InputObject.Count)
[Array]::Copy($InputObject, $arr, $InputObject.Count)
for ($i = $arr.Length - 1; $i -gt 0; $i--) {
$j = $Random.Next(0, $i + 1)
$tmp = $arr[$i]
$arr[$i] = $arr[$j]
$arr[$j] = $tmp
}
return ,$arr
}
$rand = [Random]::new(202401)
$names = 'alpha','beta','gamma','delta','epsilon'
$shuffled = Invoke-FisherYatesShuffle -InputObject $names -Random $rand
$shuffled -join ','
2) Reproducible Bytes and GUIDs
You can deterministically generate bytes, strings, and even GUIDs by drawing from the same Random stream.
$rand = [Random]::new(424242)
# Deterministic 16-byte buffer
$bytes = New-Object byte[] 16
$rand.NextBytes($bytes)
# Deterministic GUID built from those bytes
$guid = [Guid]::new($bytes)
$guid
# Deterministic alphanumeric string
$alphabet = ('a'..'z') + ('A'..'Z') + ('0'..'9')
$len = 12
$token = -join (1..$len | ForEach-Object { $alphabet[$rand.Next(0, $alphabet.Count)] })
$token
Note: System.Random is not cryptographically secure. Dont use deterministically generated secrets in production. For secrets, use cryptographically secure APIs and do not seed.
3) Parallel and Distributed Runs
[System.Random] isnt thread-safe. If you do parallel work (e.g., ForEach-Object -Parallel or jobs), create one Random per worker with derived seeds. A simple pattern is to use a master seed to produce a series of worker seeds deterministically.
param([int]$Seed = 777, [int]$Degree = 4)
$master = [Random]::new($Seed)
$workerSeeds = 1..$Degree | ForEach-Object { $master.Next() }
1..20 | ForEach-Object -Parallel {
param($seeds)
$id = $using:PSParallelInstanceId
$rand = [Random]::new($seeds[$id % $seeds.Count])
[pscustomobject]@{
Worker = $id
Sample = $rand.Next(0,1000)
}
} -ArgumentList ($workerSeeds)
This gives each parallel worker a deterministic but distinct sequence. Re-run with the same master seed and degree for identical outputs.
4) Pester: Stable Tests on Every Run
Use a fixed seed in your Pester tests or log and re-inject the seed from the environment. This makes tests reproducible locally and in CI.
# Example Pester test (Pester 5+)
param([int]$Seed = 9001)
$rand = [Random]::new($Seed)
Describe 'Customer generator' {
It 'creates deterministic IDs' {
$ids = 1..3 | ForEach-Object { $rand.Next(1000, 9999) }
$ids | Should -Be @(7710, 8039, 2167) # Example known-good snapshot
}
}
For CI pipelines, pass a seed via environment variable and log the exact value during the run:
$seed = $env:RUN_SEED
if (-not $seed) { $seed = (Get-Random -Minimum 1 -Maximum [int]::MaxValue) }
$rand = [Random]::new([int]$seed)
Write-Host "RUN_SEED=$seed" # capture in build logs
5) Convenience Wrapper
Encapsulate the pattern so your scripts dont repeat setup everywhere.
function New-DeterministicRandom {
[CmdletBinding()] param(
[Parameter()] [int]$Seed,
[switch]$Auto
)
if ($PSBoundParameters.ContainsKey('Seed')) {
return [Random]::new($Seed)
}
if ($Auto) {
# Generate and log an automatic seed
$auto = (Get-Random -Minimum 1 -Maximum [int]::MaxValue)
Write-Information "RunSeed=$auto" -InformationAction Continue
return [Random]::new($auto)
}
throw 'Provide -Seed or -Auto.'
}
# Usage
$rand = New-DeterministicRandom -Seed 1337
Security Note
- Do not use System.Random for secrets: Its predictable by design when seeded. For tokens, passwords, and keys use cryptographic APIs such as
[System.Security.Cryptography.RandomNumberGenerator]::Create()and never seed them for determinism. - Separate test data from prod secrets: Deterministic generators are perfect for fixtures and demos, not for sensitive values.
Practical Tips and Pitfalls
- Avoid per-call reseeding: Calling
new Random($Seed)inside a loop restarts the sequence each time, yielding repeated first values. - Keep the instance alive: If you need randomness across functions, pass the
$randinstance explicitly or store it in a module-level variable. - Snapshot expectations: When your golden outputs change (e.g., logic updates), update both the seed and the expected snapshots to match.
- Bias matters: For shuffles, prefer FisherYates over
Sort-Object { $rand.Next() }when uniformity matters. For most demos, the latter is acceptable and simpler. - Document the seed contract: Make it obvious in your README or function help that passing a seed guarantees reproducible output.
Summary
By seeding your random source once, reusing a single Random instance, and logging the seed, you can make generators and tests repeatable on every run. Youll get stable outputs, easier debugging, and predictable demosexactly what you want in disciplined DevOps workflows and reliable CI/CD pipelines.
Make reproducibility a default in PowerShell. For deeper patterns, recipes, and production-grade guidance, see the PowerShell Advanced CookBook: PowerShell Advanced CookBook.
What you get: repeatable tests, stable outputs, easier debugging, predictable demos.