Junction points, symbolic links and hard links let one path on an NTFS volume point to another file or folder. Windows uses them itself (C:\Documents and Settings is a junction to C:\Users), and as a sysadmin you can use them to keep paths stable while the content behind them changes. In Quickly view all symbolic links, junction points and hard links in a folder using PowerShell I showed how to find them. This post is about creating and removing them, using PowerShell's New-Item and Remove-Item cmdlets and the good old mklink command.
Junctions, symbolic links and hard links: which one do you need?
- A junction (or junction point) is a link to a directory. It can point to a folder on another local volume, but not to a network share. Creating a junction does not require administrator privileges, which makes it the easiest choice for redirecting folders on a server.
- A symbolic link can point to a file or a directory, on a local volume or on a UNC path, and it can use a relative target. By default only administrators can create symbolic links, unless Developer Mode is enabled or the account has the Create symbolic links user right.
- A hard link is a second name for the same file. It works for files only and both names must live on the same volume. There is no "original" and "link": the data stays on disk until the last name pointing to it is removed.
Create a junction with New-Item
Since PowerShell 5.0, New-Item knows the item types Junction, SymbolicLink and HardLink. You give it the path of the link with -Path and the existing folder with -Target (or its alias -Value). The link itself must not exist yet, the target should.
Say you have three versions of a software package in C:\Software. Create a default, a new and an old junction pointing to them:
Set-Location -Path C:\Software
New-Item -ItemType Junction -Path default -Target 'C:\Software\package.v4'
New-Item -ItemType Junction -Path new -Target 'C:\Software\package.v5'
New-Item -ItemType Junction -Path old -Target 'C:\Software\package.v3'
Verify the result with Get-ChildItem. The LinkType and Target properties tell you what is what:
Get-ChildItem -Path C:\Software | Select-Object Name, LinkType, Target
Create symbolic links and hard links
Symbolic links and hard links are created the same way, only the -ItemType differs. Run this from an elevated PowerShell prompt, because of the symbolic link:
# Directory symbolic link, may point to a UNC path
New-Item -ItemType SymbolicLink -Path C:\Software\shared -Target '\\fileserver\software\package.v5'
# File symbolic link
New-Item -ItemType SymbolicLink -Path C:\Software\php.ini -Target 'C:\Software\package.v5\php.ini'
# Hard link, file only and on the same volume
New-Item -ItemType HardLink -Path C:\Software\settings.xml -Target 'C:\Software\package.v5\settings.xml'
To see all names of a hard linked file, use fsutil hardlink list C:\Software\settings.xml.
Create a junction with mklink
Before PowerShell had this, there was mklink. It still works fine, and you will find it in a lot of older scripts and documentation. Note the order of the arguments: first the link, then the target. Use /J for a junction, /D for a directory symbolic link, /H for a hard link, and no switch for a file symbolic link.
mklink /J "C:\Software\Newest" "C:\Software\package.v6"
mklink is not an executable but a built-in command of cmd.exe. That is why calling it directly from PowerShell fails with "The term 'mklink' is not recognized". Hand it over to cmd.exe with /c:
cmd.exe /c mklink /J "C:\Software\Newest" "C:\Software\package.v6"
Example: switch software versions without touching configuration
This is where junctions really pay off. In IIS, every website with a PHP handler mapping has the full path to php-cgi.exe in its scriptProcessor attribute. If that path contains a version number, upgrading PHP means editing the handler mapping of every website on the server. With hundreds of websites, you do not want that.
So you never reference a versioned folder directly. Install every version in its own folder, and let IIS use a stable path like C:\Software\php\default\php-cgi.exe, where default is a junction to the version you want. Upgrading, or rolling back, is then a matter of pointing the junction to another folder:
Set-Location -Path C:\Software\php
(Get-Item -Path default).Delete()
New-Item -ItemType Junction -Path default -Target 'C:\Software\php\8.3.12'
The new and old junctions give you a test path and a fallback path on the same server, for example for a few websites that you move to the new version first. Running php-cgi.exe FastCGI processes keep the old binaries loaded, so recycle the application pools that use the handler afterwards. On ITFAQ.nl I wrote how to recycle application pools with PowerShell if they use a certain scriptProcessor (in Dutch).
The same trick works for the Windows Update cache. In my Windows Server disk cleanup post, I move the C:\Windows\SoftwareDistribution folder to another drive and leave a junction in its place.
Remove a junction with Remove-Item or .Delete()
Removing a link removes the link, not the folder it points to. In PowerShell 7 that is exactly what Remove-Item does:
Remove-Item -Path C:\Software\old
Windows PowerShell 5.1 is less predictable. Remove-Item on a junction to a non-empty folder asks for confirmation because it thinks the item has children, and with -Recurse it may follow the link and delete files in the target folder. Safer is the .Delete() method of the DirectoryInfo object Get-Item returns. It removes the reparse point only:
$junction = Get-Item -Path C:\Software\old
$junction.Delete()
From cmd.exe, rmdir C:\Software\old (without /s) does the same. For a file symbolic link or a hard link, Remove-Item removes that one name and leaves the target, or the other hard links, untouched.
Things to keep in mind
By default Robocopy follows junctions and copies the content behind them, or skips them with /XJ. Before migrating a file server, list all links first and recreate them on the new server.
A junction stores an absolute path. If you move or rename the target folder, the junction breaks and points to nothing. Check the Target property after big changes, and always delete and recreate a junction rather than renaming the folder it points to.