Powershell: Fix Remove-Item <symbolic link to directory>

Created on 4 Mar 2016  路  58Comments  路  Source: PowerShell/PowerShell

If you make a symbolic link to a directory on Windows (with administrative privileges of course), and then try to delete said link, you get this:

|Y> Yes |A> Yes to All |N> No |L> No to All |S> Suspend [Default is (Y]
PS C:\Users\anschwa\src\PowerShell> remove-item -Force .\foobar\
Confirm
The item at C:\Users\anschwa\src\PowerShell\foobar\ has children and the Recurse parameter was not specified. If you con
tinue, all children will be removed with the item. Are you sure you want to continue?

|Y> Yes |A> Yes to All |N> No |L> No to All |S> Suspend [Default is (Y]
n
PS C:\Users\anschwa\src\PowerShell> remove-item -Force .\foobar\ -Recurse
remove-item : There is a mismatch between the tag specified in the request and the tag present in the reparse point
At line:1 char:1
+ remove-item -Force .\foobar\ -Recurse
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Remove-Item], Win32Exception
    + FullyQualifiedErrorId : System.ComponentModel.Win32Exception,Microsoft.PowerShell.Commands.RemoveItemCommand

PS C:\Users\anschwa\src\PowerShell> $error[0].Exception.StackTrace
   at Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods.DeleteJunction(String junctionPath) in C:\Users\
anschwa\src\PowerShell\src\monad\monad\src\namespaces\FileSystemProvider.cs:line 8447
   at Microsoft.PowerShell.Commands.FileSystemProvider.RemoveDirectoryInfoItem(DirectoryInfo directory, Boolean recurse,
 Boolean force, Boolean rootOfRemoval) in C:\Users\anschwa\src\PowerShell\src\monad\monad\src\namespaces\FileSystemProvi
der.cs:line 2859
   at Microsoft.PowerShell.Commands.FileSystemProvider.RemoveItem(String path, Boolean recurse) in C:\Users\anschwa\src\
PowerShell\src\monad\monad\src\namespaces\FileSystemProvider.cs:line 2733
   at System.Management.Automation.Provider.ContainerCmdletProvider.RemoveItem(String path, Boolean recurse, CmdletProvi
derContext context) in C:\Users\anschwa\src\PowerShell\src\monad\monad\src\namespaces\ContainerProviderBase.cs:line 422
   at System.Management.Automation.SessionStateInternal.RemoveItem(CmdletProvider providerInstance, String path, Boolean
 recurse, CmdletProviderContext context) in C:\Users\anschwa\src\PowerShell\src\monad\monad\src\engine\SessionStateConta
iner.cs:line 1152

Remove-Item is thinking the link is a directory and so failing to delete it.

If the code is changed such that File.Delete(junction) is used instead of native calls, you get:

PS C:\Users\anschwa\src\PowerShell> rm .\foobar\ -Force
Confirm
The item at C:\Users\anschwa\src\PowerShell\foobar\ has children and the Recurse parameter was not specified. If you con
tinue, all children will be removed with the item. Are you sure you want to continue?

|Y> Yes |A> Yes to All |N> No |L> No to All |S> Suspend [Default is (Y]
y
rm : Index (zero based) must be greater than or equal to zero and less than the size of the argument list.
At line:1 char:1
+ rm .\foobar\ -Force
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Remove-Item], FormatException
    + FullyQualifiedErrorId : System.FormatException,Microsoft.PowerShell.Commands.RemoveItemCommand

PS C:\Users\anschwa\src\PowerShell> $error[0].Exception.StackTrace
   at System.Text.StringBuilder.AppendFormatHelper(IFormatProvider provider, String format, ParamsArray args)
   at System.String.FormatHelper(IFormatProvider provider, String format, ParamsArray args)
   at System.String.Format(IFormatProvider provider, String format, Object arg0)
   at System.Management.Automation.Internal.StringUtil.Format(String formatSpec, Object o) in C:\Users\anschwa\src\Power
Shell\src\monad\monad\src\utils\StringUtil.cs:line 18
   at Microsoft.PowerShell.Commands.FileSystemProvider.RemoveDirectoryInfoItem(DirectoryInfo directory, Boolean recurse,
 Boolean force, Boolean rootOfRemoval) in C:\Users\anschwa\src\PowerShell\src\monad\monad\src\namespaces\FileSystemProvi
der.cs:line 2863
   at Microsoft.PowerShell.Commands.FileSystemProvider.RemoveItem(String path, Boolean recurse) in C:\Users\anschwa\src\
PowerShell\src\monad\monad\src\namespaces\FileSystemProvider.cs:line 2733
   at System.Management.Automation.Provider.ContainerCmdletProvider.RemoveItem(String path, Boolean recurse, CmdletProvi
derContext context) in C:\Users\anschwa\src\PowerShell\src\monad\monad\src\namespaces\ContainerProviderBase.cs:line 422
   at System.Management.Automation.SessionStateInternal.RemoveItem(CmdletProvider providerInstance, String path, Boolean
 recurse, CmdletProviderContext context) in C:\Users\anschwa\src\PowerShell\src\monad\monad\src\engine\SessionStateConta
iner.cs:line 1152

It fails even sooner (I think because there aren't actually any children to pass delete recursively, so it gets an empty list; just a guess).

If you use Directory.Delete(junctionPath), it works just fine!

Area-Cmdlets Committee-Reviewed Issue-Bug OS-Windows Resolution-Fixed

Most helpful comment

If anyone stumbles across this issue as I did trying to remove reparsepoints without deleting the underlying file use

(get-item $removethispathreparsepoint).Delete()

All 58 comments

So, Remove-Item needs to a) not ask to recursively delete a symbolic link to a directory, and b) not recursively delete the directory, just delete the link. There's a right way to do this and a quick way to do this; as it is now, the FileSystemProvider code is all hacked up, and needs to be fixed.

I'm going to disable the symbolic link tests for Windows until we can solve this correctly.

Problem is, sym links behave differently on Windows vs. Linux. For a symlink that points to a directory on Linux, deleting the link does not impact the directory it points to. But I believe for windows, symlinks are much closer to hard links, and deleting it will delete what the link points to.

Do we have latest/greatest fix from CoreFx?

@palladia Can you test this out again? We have the latest and greatest .NET Core packages.

Just FYI, this works correctly on Linux, except that it asks to remove the item recursively (even though all it does is remove the link).

Moving this to the next milestone as we're pending ported DVT/BRT tests.

We have the tests available now.

Have we tried them? Where are they?

Do you know if the work is complete? The comment says it's still a work-in-progress. I downloaded it onto Linux, and 11 tests fail.

repro when running Remove-Item -Recurse -Force on a folder which contains a symlink to another folder.
Name Value


PSVersion 5.1.14951.1001
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.14951.1001
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1

This bug is existent in Windows PowerShell targetting FullCLR, re-opening.

@joeyaiello this bug has been fixed in PowerShell Core per #920; it should probably be slated for backport to the next PowerShell Full release.

@andschwa ps-committee needs to see a test that shows the expected behavior on Windows and Linux

@stevel-msft I don't follow.

@andschwa when we reviewed this yesterday, we didn't understand the expected behavior on Windows and Linux. we tried a few things with symlinks on both and it seemed consistent in that the soft-link got removed and not the target. perhaps making it more clear with expected/actual on both Windows and Linux would help us.

@SteveL-MSFT Correct, the issue is fixed in PowerShell 6.0, there should be symlink tests in the test directory. The issue is that PowerShell 5 on Windows is broken, per @asklar's bug report.

@andschwa we tried to repro it on Windows PowerShell 5.1 as well and it seemed to have the same behavior as PowerShell Core 6.0 on Windows. Could you provide a simple repro?

@asklar would you please provide them with a simple repro?

I ran into this issue today, being unable to remove a symlink on Windows Server 2016. Here is a stepby-step PowerShell session repro case that hopefully illustrates the issue:

PS C:\> & cmd /c mklink /d c:\foo c:\inetpub
symbolic link created for c:\foo <<===>> c:\inetpub
PS C:\> remove-item c:\foo

Confirm
The item at C:\foo has children and the Recurse parameter was not specified. If you continue, all children will be removed with the
 item. Are you sure you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
remove-item : C:\foo is an NTFS junction point. Use the Force parameter to delete or modify this object.
At line:1 char:1
+ remove-item c:\foo
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (C:\foo:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : DirectoryNotEmpty,Microsoft.PowerShell.Commands.RemoveItemCommand

PS C:\> remove-item -force c:\foo

Confirm
The item at C:\foo has children and the Recurse parameter was not specified. If you continue, all children will be removed with the
 item. Are you sure you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
remove-item : There is a mismatch between the tag specified in the request and the tag present in the reparse point
At line:1 char:1
+ remove-item -force c:\foo
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Remove-Item], Win32Exception
    + FullyQualifiedErrorId : System.ComponentModel.Win32Exception,Microsoft.PowerShell.Commands.RemoveItemCommand

PS C:\> (get-host).version

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      14393  206

@PowerShell/powershell-committee thanks @sandersaares for the repro, we need @joeyaiello to see if this repros on Core and if so, we should fix this

Happening on Win10 annivesary with Powershell 5.1 trying to remove folder left by docker installation


PS C:\Users\admin> Remove-Item C:\ProgramData\docker\ -Force -Recurse
Remove-Item : There is a mismatch between the tag specified in the request and the tag present in the reparse point
At line:1 char:1
+ Remove-Item C:\ProgramData\docker\ -Force -Recurse
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Remove-Item], Win32Exception
    + FullyQualifiedErrorId : System.ComponentModel.Win32Exception,Microsoft.PowerShell.Commands.RemoveItemCommand

@sandersaares thanks for the more simple repro! I can confirm that this does happen, but the hunch from @palladia that the children actually get deleted is incorrect.

So to clarify, even after specifying -Force, only the symbolic link is deleted and not the contents of the folder to which the link points.

The behavior that definitely needs to be fixed here is giving the confirmation prompt about the children and the Recurse parameter. The behavior that should be discussed is whether we should require a -Force for symbolic links. I would say 'no', but that would likely be a breaking change (so I'm re-adding Review - Committee here).

And this is all on the latest 6.0 alpha:

Name                           Value
----                           -----
GitCommitId                    v6.0.0-alpha.13
PSRemotingProtocolVersion      2.3
PSEdition                      Core
WSManStackVersion              3.0
CLRVersion
BuildVersion                   3.0.0.0
PSVersion                      6.0.0-alpha
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
SerializationVersion           1.1.0.1

@artisticcheese that looks like a very different error message. Could you file another bug? And, if possible, repro on 6.0 to make sure it's happening on PowerShell Core as well?

Note that my example also gave the same error message as @artisticcheese got, after adding the -Force parameter.

And to be clear, the symlink was never actually deleted by Remove-Item. I have to drop down to cmd /c rmdir /s /q c:\foo to actually delete it.

or [IO.Directory]::Delete('c:\foo')
But it is a lot of typing ...

@PowerShell/powershell-committee agrees that -force and message only relevant to removing the folder should not be there for removing a folder symlink as it should be the same as removing a file

So in other words, we shouldn't require the use of -Force to remove a symlink, and we should not traverse the symlink at remove-time to delete the files that it targets. Treat the symlink itself as you would any other file.

I'm running powershell 5.1 on Win10. The use of "Remove-Item" on a directory fails due to some subdirectories containing symlinks. Is there a way to delete the directory from powershell 5.1?

I must be doing something wrong. Anyway, I found a way to get it done, at least.
From a powershell script, do

[System.IO.Directory]::Delete("C:\folder\to\delete", $true)

However, from a powershell terminal session, you have to:

cmd /c "rmdir C:\folder\to\delete"

You cannot use the latter in a powershell script and you cannot use the first on a powershell terminal session.

@Schroedingers-Cat I've been trying this out on a Windows 10 box using PowerShell 5.1:

PS > $PsVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.14393.953
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.14393.953
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

I'm seeing that the methods you mention work the same whether run from the PowerShell prompt or from a script.

I've been testing both, from the prompt and within script, using multiple link types within the sub-directory to be removed, and the only issue I've seen is when using the [System.IO.Directory]::Delete method when the sub-directory contains a Junction. In that case, the Delete call throws an error saying access to the junction is denied. It actually isn't, and the junction is in fact deleted, but because the error "occurred" the sub-directory is not deleted. Calling Delete again will delete the sub-directory.

For all other types of links, both methods seem to work equally well.

Just tested on a different machine and both methods work there.
Well, maybe something was wrong with the paths-variable when I encountered the issue first.

Btw. when will we be able to use "Remove-Item" instead?

@Schroedingers-Cat In general, Remove-Item is working in PowerShell 6. I was able to remove a sub-directory full of all kinds of link types with

Remove-Item sub -Force -Recurse

What we're working on here is the correct use (or non-use) of -Force and -Recurse when removing a link directly.

@joeyaiello Just to be sure I'm doing the right thing. Using Remove-Item symlink on a either a directory symbolic link or a junction will remove the link but not attempt to traverse the directory that symlink points to. What, then, is the expected behavior if the cmdlet is invoked with -Recurse? Should it delete the contents of the linked-to directory then delete the link, or ignore the -Recurse flag and simply delete the link?

There has to be a precedence for this in the unix world.
But anything but simply deleting the link seems dangerous.

My expectations:
Remove-Item symlink - remove only symlink.
Remove-Item symlink -Recurse - remove symlink and target too with confirmation by default (w/o with -Force)

The unix rm command even with the -r switch removes only the link itself. That's my preference for how the cmdlet should work as well, but I wasn't sure how well silently ignoring a cmdlet option would go over.

There are already two differing opinions, which is a big part of why I posed the question.

I agree that this is special case and we should follow Unix rm .

I wasn't sure how well silently ignoring a cmdlet option would go over

It seems we usually use non-terminating error. User always can suppress it by "SilentlyContinue".

@jeffbi: that sounds right to me. And a non-terminating error still feels a little weird. For example, it may be that you're deleting a regular folder full of folders full of symlinks, and you want to use the -Recurse parameter to delete all of the symlinks. That shouldn't really result in an error just because you don't traverse the symlinks.

If you're absolutely married to the idea, @iSazonov, a warning might be in order, but I don't think anything more than that is necessary.

I've put up a PR for this. At the moment the cmdlet silently ignores the -Recurse option it if is given. I'll change that if required.

@jeffbi be careful with that, as rm -r linktofolder does _not_ recursively delete the contents of the linked folder, unless you add a /, that is, rm -r linktofolder/ will recursively delete the contents of the linked folder. I think it treats the link as a link when the slash is missing, and follows the link if the trailing slash is added.

@andschwa On my Linux box rm -r linktofolder/ behaves pretty much as you describe, with the addition of an error message which is displayed despite having emptied the target directory:

$ tree
.
|-- fred
|   `-- file.txt
`-- fred-sl -> fred

2 directories, 1 file
$ rm -r fred-sl/
rm: cannot remove 'fred-sl/': Not a directory
$ tree
.
|-- fred
`-- fred-sl -> fred

2 directories, 0 files

So how should this knowledge affect the behavior of Remove-Item? You made a comment back in August on #1775 that suggests that users shouldn't have to use a trailing slash to get into a symlink.

A comment from @PaulHigin in a code review on the PR has brought up another question: should junctions be treated differently than file symlinks or directory symlinks?

Before the PR, junctions were treated differently; they required both -Recurse (or answering Y to the prompt) and -Force to remove the junction. After the PR, junctions are treated like other types of symbolic link.

What is the preferred behavior here?

It is better to stick to one pattern.

@joeyaiello

And a non-terminating error still feels a little weird

I could be wrong it seems we use non-terminating errors instead of warnings.

@joeyaiello Regarding @andschwa's comments about rm -r linktofolder/, PowerShell's Remove-Item -Recurse linktofolder/ currently errors out on both Linux and Windows, but in different ways. On Linux the error is not a directory, on Windows it's Cannot find path.

I think this should be a separate issue and not to be addressed in this one. What do you think?

Quoted from the PR:

When Remove-Item is used to remove a symbolic link in Windows, only the link itself is removed. The -Force switch is no longer required.
If the directory pointed to by the link has child items, the cmdlet no longer prompts the user to remove the child items---those child items are not removed. The -Recurse switch, if given, is ignored.
This brings Remove-Item more in line with the behavior of the 'rm' command on Unix.

@jeffbi #3637 has been merged, but it seems it doesn't resolve all the concerns in this issue. Are you going to open a separate issue for the behavior of Remove-Item -Recurse linktofolder/ vs. rm -r linktofolder/?

@daxian-dbw I believe it should have a separate issue, and I posed the question yesterday. If you'd like me to go ahead and open an issue for it, I'll do so.

@jeffbi you should open a new issue and reference this one. We should then close this one.

@jeffbi Thanks for the fix! I closed the Issue to continue in #3674

Has it been resolved? I still get this issue

Same issue here.

@manigandan-jegannathan-developer @Alwandy Please clarify what kind of problem are you talking about? It is better open new issue with repo steps and link the issue.

Get-ChildItem $somepath -Attributes ReparsePoint | % { $_.Delete() }

I still get this issue.

UPDATE: worked out that the link was going to a directory that didn't exist. Using cmd was the only way I could find to remove the link.

@amay0048 Did you look #3674?

If anyone stumbles across this issue as I did trying to remove reparsepoints without deleting the underlying file use

(get-item $removethispathreparsepoint).Delete()

@amay0048 can you open a new issue where the link points to a non-existing directory?

Looks like Windows 10 1809 can't delete profiles even through wmi. Rm -r in powershell won't work either. It looks like the MicrsoftOfficeHub app is making weird links under "AppData\Local\Packages\Microsoft.MicrosoftOfficeHub_8wekyb3d8bbwe\LocalCache". Remove-item says "is an NTFS junction point. Use the Force parameter to delete or modify this object." Remove-item -force says "There is a mismatch between the tag specified in the request and the tag present in the reparse point"

"cmd /c rmdir /s /q" works ok.

Building upon what @erichiller nicely shared, I created a PS function that uses pass-through arguments to customize the deletion of symlinks.

Used to remove links made with mklink

function Remove-SymLinks {
    Get-ChildItem -Force -ErrorAction Stop @Args | Where-Object { if($_.Attributes -match "ReparsePoint"){$_.Delete()} }
}

Example Usage
Remove-SymLinks -Remove all links in the current directory
Remove-SymlLinks -Path .\Builds -Recurse -Recursively Remove all links in the Builds Directory

Disclaimer, I am a C# expert and a wannabe PowerShell weekend warrior so be careful with that Recurse option.

Was this page helpful?
0 / 5 - 0 ratings