Skip to content
5 min read

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 $TestDrive variable 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 to TestDrive: and let Pester clean up.

Assuming TestDrive persists 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.