Safe File and Registry Tests with TestDrive and TestRegistry
- powershell
- pester
- testing
- testdrive
- testregistry
- filesystem
Plenty of useful PowerShell touches the world: it writes a config file, drops a log, sets a registry value. You want to test that behavior—but you don't want a test run littering your disk or, worse, clobbering a real file. Pester gives you a disposable sandbox so your side-effecting code can run safely and clean up after itself.
What you'll learn
- What
TestDrive:is and how it auto-cleans - How to write and read files under the sandbox
- Using the
$TestDrivevariable to build paths TestRegistry:for code that touches the Windows registry- Why the sandbox beats temp folders you manage yourself
Meet TestDrive:
TestDrive: is a PSDrive that Pester creates for you, backed by a fresh temporary folder. You read and write under it exactly like any other path — TestDrive:\config.json, TestDrive:/logs/app.log, and so on. The magic is the lifecycle: Pester makes it before your tests run and deletes it automatically afterward. You never write cleanup code, and nothing leaks onto your real disk.
Describe 'TestDrive basics' {
It 'starts empty and accepts a file' {
$path = 'TestDrive:\hello.txt'
Set-Content -Path $path -Value 'hi'
Get-Content -Path $path | Should -Be 'hi'
}
}The scope is per top-level Describe/file: the contents are cleared so tests do not bleed into each other, and the whole drive is gone once the run ends.
Testing a function that writes a file
Here is a small function that saves settings to disk. Notice it takes the target folder as a parameter — that one design choice is what makes it testable.
function Save-AppConfig {
param(
[Parameter(Mandatory)][string]$Path,
[Parameter(Mandatory)][hashtable]$Settings
)
$Settings | ConvertTo-Json | Set-Content -Path $Path -Encoding UTF8
}The test points it straight at the sandbox:
Describe 'Save-AppConfig' {
It 'writes the settings as JSON' {
$configPath = 'TestDrive:\config.json'
Save-AppConfig -Path $configPath -Settings @{ Theme = 'Dark'; Retries = 3 }
Test-Path $configPath | Should -BeTrue
$loaded = Get-Content $configPath -Raw | ConvertFrom-Json
$loaded.Theme | Should -Be 'Dark'
$loaded.Retries | Should -Be 3
}
}The file is created, read back, asserted on, and then it vanishes when the run finishes. Run it twice in a row and the second run starts just as clean as the first.
The $TestDrive variable
The TestDrive: drive string is great for PowerShell-native cmdlets like Get-Content and Test-Path. But some commands — and most .NET methods — do not understand PSDrive paths. For those you need a real filesystem path, and that is what the $TestDrive variable gives you: the actual folder on disk behind the drive.
Describe 'Using $TestDrive for a real path' {
It 'builds a nested path that .NET can use' {
$folder = Join-Path $TestDrive 'logs'
New-Item -ItemType Directory -Path $folder | Out-Null
$file = Join-Path $folder 'app.log'
[System.IO.File]::WriteAllText($file, 'started')
[System.IO.File]::ReadAllText($file) | Should -Be 'started'
}
}Quick guide: use the TestDrive: string with PowerShell cmdlets, and the $TestDrive variable with Join-Path, .NET methods, or anything that needs a plain path.
TestRegistry: for registry code (Windows)
If your code reads or writes the registry, TestRegistry: is the same idea applied to a throwaway registry key. Pester creates a temporary key, exposes it as the TestRegistry: drive, and removes it when the run ends — so you can test registry logic without poisoning HKCU or HKLM. This is Windows-only; the drive does not exist on Linux or macOS.
Describe 'Registry write' -Skip:(-not $IsWindows) {
It 'stores and reads back a value' {
New-Item -Path 'TestRegistry:\MyApp' | Out-Null
Set-ItemProperty -Path 'TestRegistry:\MyApp' -Name 'Version' -Value '2.0'
$value = Get-ItemProperty -Path 'TestRegistry:\MyApp' -Name 'Version'
$value.Version | Should -Be '2.0'
}
}The -Skip:(-not $IsWindows) guard keeps the test from failing on non-Windows machines. ($IsWindows exists in PowerShell 7; it is treated as $null — falsey — in Windows PowerShell 5.1, where you are on Windows anyway.)
Why not just use a temp folder?
You could write to $env:TEMP\my-test-$(New-Guid) and delete it in AfterAll. People do, and it goes wrong in familiar ways: a failing test skips the cleanup and leaves junk behind; two parallel runs collide; the cleanup code itself has a bug. TestDrive: and TestRegistry: hand all of that to Pester — fresh sandbox, guaranteed teardown, even when a test blows up. Less code, fewer surprises.
Try it yourself
Write a function Write-Log that takes a -Path and a -Message and appends a timestamped line to the file. Then write a test that calls it with a TestDrive: path, asserts the file exists with Test-Path ... | Should -BeTrue, reads it back with Get-Content, and asserts the message is present with Should -Match. Confirm nothing lands in your repo or $env:TEMP.
Common mistakes
Writing artifacts to the repo or
$env:TEMP. Hard-coded real paths pollute your disk and make tests order-dependent. Send all test output toTestDrive:and let Pester clean up.Assuming
TestDrivepersists across files. The sandbox is scoped to the run and reset between top-level blocks — never use it to pass data from one test file to another.Forgetting
TestRegistry:is Windows-only. It does not exist on Linux or macOS. Guard registry tests with-Skip:(-not $IsWindows)so cross-platform runs stay green.
Recap
TestDrive: and $TestDrive let you exercise file-writing code in a self-cleaning sandbox; TestRegistry: does the same for Windows registry code. Use the drive string with cmdlets and the variable for real paths, and let Pester handle teardown so failures never leave a mess. Next we tackle the copy-paste smell: five nearly identical tests that differ only in their numbers.
Next up: Part 15 — Data-Driven Tests with -ForEach and -TestCases.