← ./articles

Use PowerShell New-Item for node_modules Junctions on Windows

When moving node_modules outside a OneDrive-synced project, many examples reach for mklink /J.

That works in simple ASCII paths. But on real Windows machines, project paths often include Desktop, localized folder names, spaces, or non-ASCII characters. In those cases, invoking mklink through cmd.exe from PowerShell can fail or garble the path.

For automation, prefer PowerShell's native cmdlet:

New-Item -ItemType Junction -Path $JunctionPath -Target $TargetPath

mklink is a cmd.exe built-in command, not a PowerShell cmdlet.

That means this pattern crosses a shell boundary:

cmd.exe /c "mklink /J `"$JunctionPath`" `"$TargetPath`""

Now you have to get quoting, escaping, and encoding right. With non-ASCII paths, that boundary can turn a valid path into a corrupted one. The error may claim the path does not exist even though PowerShell can read it.

Why New-Item is better

New-Item runs inside PowerShell and receives PowerShell strings directly.

New-Item -ItemType Junction -Path $JunctionPath -Target $TargetPath -Force

It avoids the cmd.exe quoting layer and handles Windows Unicode paths more predictably.

A reusable script shape

Use a script that can be run repeatedly:

$ErrorActionPreference = "Stop"

$ProjectRoot = Split-Path -Parent $PSScriptRoot
$JunctionPath = Join-Path $ProjectRoot "node_modules"
$TargetPath = "<external-store>\my-project\node_modules"

New-Item -ItemType Directory -Path $TargetPath -Force | Out-Null

if (Test-Path $JunctionPath) {
  $item = Get-Item $JunctionPath -Force
  if ($item.LinkType -eq "Junction" -and $item.Target -eq $TargetPath) {
    Write-Host "node_modules junction already correct"
    exit 0
  }

  Remove-Item $JunctionPath -Recurse -Force
}

New-Item -ItemType Junction -Path $JunctionPath -Target $TargetPath | Out-Null
Write-Host "node_modules junction created"

The key is idempotency. Running the script twice should be safe.

Add it to package scripts

Use npm pre-scripts so the junction is checked before build commands:

{
  "scripts": {
    "predev": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\ensure-node-modules.ps1",
    "prebuild": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\ensure-node-modules.ps1"
  }
}

This matters because npm can replace a junction with a real folder during install. The script should catch that before development or build starts.

Summary

For node_modules junctions on Windows, New-Item -ItemType Junction is the automation-friendly default.

Use mklink for manual one-off terminal work if you want, but prefer PowerShell cmdlets in scripts. They reduce quoting problems and handle localized paths more reliably.

References