@@ -1890,9 +1890,14 @@ private void Dir(
18901890 }
18911891
18921892 bool hidden = false ;
1893+ bool checkReparsePoint = true ;
18931894 if ( ! Force )
18941895 {
18951896 hidden = ( recursiveDirectory . Attributes & FileAttributes . Hidden ) != 0 ;
1897+
1898+ // We've already taken the expense of initializing the Attributes property here,
1899+ // so we can use that to avoid needing to call IsReparsePointLikeSymlink() later.
1900+ checkReparsePoint = ( recursiveDirectory . Attributes & FileAttributes . ReparsePoint ) != 0 ;
18961901 }
18971902
18981903 // if "Hidden" is explicitly specified anywhere in the attribute filter, then override
@@ -1906,7 +1911,7 @@ private void Dir(
19061911 // c) it is not a reparse point with a target (not OneDrive or an AppX link).
19071912 if ( tracker == null )
19081913 {
1909- if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( recursiveDirectory ) )
1914+ if ( checkReparsePoint && InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( recursiveDirectory ) )
19101915 {
19111916 continue ;
19121917 }
@@ -2058,7 +2063,7 @@ string ToModeString(FileSystemInfo fileSystemInfo)
20582063 public static string NameString ( PSObject instance )
20592064 {
20602065 return instance ? . BaseObject is FileSystemInfo fileInfo
2061- ? InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( fileInfo )
2066+ ? InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( fileInfo )
20622067 ? $ "{ fileInfo . Name } -> { InternalSymbolicLinkLinkCodeMethods . GetTarget ( instance ) } "
20632068 : fileInfo . Name
20642069 : string . Empty ;
@@ -3098,22 +3103,31 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool
30983103 continueRemoval = ShouldProcess ( directory . FullName , action ) ;
30993104 }
31003105
3101- if ( directory . Attributes . HasFlag ( FileAttributes . ReparsePoint ) )
3106+ if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( directory ) )
31023107 {
3108+ void WriteErrorHelper ( Exception exception )
3109+ {
3110+ WriteError ( new ErrorRecord ( exception , errorId : "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ) ) ;
3111+ }
3112+
31033113 try
31043114 {
3105- // TODO:
3106- // Different symlinks seem to vary by behavior.
3107- // In particular, OneDrive symlinks won't remove without recurse,
3108- // but the .NET API here does not allow us to distinguish them.
3109- // We may need to revisit using p/Invokes here to get the right behavior
3110- directory . Delete ( ) ;
3115+ if ( InternalTestHooks . OneDriveTestOn )
3116+ {
3117+ WriteErrorHelper ( new IOException ( ) ) ;
3118+ return ;
3119+ }
3120+ else
3121+ {
3122+ // Name surrogates should just be detached.
3123+ directory . Delete ( ) ;
3124+ }
31113125 }
31123126 catch ( Exception e )
31133127 {
31143128 string error = StringUtil . Format ( FileSystemProviderStrings . CannotRemoveItem , directory . FullName , e . Message ) ;
31153129 var exception = new IOException ( error , e ) ;
3116- WriteError ( new ErrorRecord ( exception , errorId : "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ) ) ;
3130+ WriteErrorHelper ( exception ) ;
31173131 }
31183132
31193133 return ;
@@ -8024,7 +8038,7 @@ protected override bool ReleaseHandle()
80248038 }
80258039
80268040 // SetLastError is false as the use of this API doesn't not require GetLastError() to be called
8027- [ DllImport ( PinvokeDllNames . FindFirstFileDllName , EntryPoint = "FindFirstFileExW" , SetLastError = false , CharSet = CharSet . Unicode ) ]
8041+ [ DllImport ( PinvokeDllNames . FindFirstFileDllName , EntryPoint = "FindFirstFileExW" , SetLastError = true , CharSet = CharSet . Unicode ) ]
80288042 private static extern SafeFindHandle FindFirstFileEx ( string lpFileName , FINDEX_INFO_LEVELS fInfoLevelId , ref WIN32_FIND_DATA lpFindFileData , FINDEX_SEARCH_OPS fSearchOp , IntPtr lpSearchFilter , int dwAdditionalFlags ) ;
80298043
80308044 internal enum FINDEX_INFO_LEVELS : uint
@@ -8215,28 +8229,50 @@ internal static bool IsReparsePoint(FileSystemInfo fileInfo)
82158229 return fileInfo . Attributes . HasFlag ( System . IO . FileAttributes . ReparsePoint ) ;
82168230 }
82178231
8218- internal static bool IsReparsePointWithTarget ( FileSystemInfo fileInfo )
8232+ internal static bool IsReparsePointLikeSymlink ( FileSystemInfo fileInfo )
82198233 {
8220- if ( ! IsReparsePoint ( fileInfo ) )
8234+ #if UNIX
8235+ // Reparse point on Unix is a symlink.
8236+ return IsReparsePoint ( fileInfo ) ;
8237+ #else
8238+ if ( InternalTestHooks . OneDriveTestOn && fileInfo . Name == InternalTestHooks . OneDriveTestSymlinkName )
82218239 {
8222- return false ;
8240+ return ! InternalTestHooks . OneDriveTestRecurseOn ;
82238241 }
8224- #if ! UNIX
8225- // It is a reparse point and we should check some reparse point tags.
8226- var data = new WIN32_FIND_DATA ( ) ;
8227- using ( var handle = FindFirstFileEx ( fileInfo . FullName , FINDEX_INFO_LEVELS . FindExInfoBasic , ref data , FINDEX_SEARCH_OPS . FindExSearchNameMatch , IntPtr . Zero , 0 ) )
8242+
8243+ WIN32_FIND_DATA data = default ;
8244+ string fullPath = Path . TrimEndingDirectorySeparator ( fileInfo . FullName ) ;
8245+ using ( var handle = FindFirstFileEx ( fullPath , FINDEX_INFO_LEVELS . FindExInfoBasic , ref data , FINDEX_SEARCH_OPS . FindExSearchNameMatch , IntPtr . Zero , 0 ) )
82288246 {
8247+ if ( handle . IsInvalid )
8248+ {
8249+ // Our handle could be invalidated by something else touching the filesystem,
8250+ // so ensure we deal with that possibility here
8251+ int lastError = Marshal . GetLastWin32Error ( ) ;
8252+ throw new Win32Exception ( lastError ) ;
8253+ }
8254+
8255+ // We already have the file attribute information from our Win32 call,
8256+ // so no need to take the expense of the FileInfo.FileAttributes call
8257+ const int FILE_ATTRIBUTE_REPARSE_POINT = 0x0400 ;
8258+ if ( ( data . dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) == 0 )
8259+ {
8260+ // Not a reparse point.
8261+ return false ;
8262+ }
8263+
82298264 // The name surrogate bit 0x20000000 is defined in https://docs.microsoft.com/windows/win32/fileio/reparse-point-tags
82308265 // Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem
82318266 // (like symlinks and mount points).
82328267 // In the case of OneDrive, they are not name surrogates and would be safe to recurse into.
8233- if ( ! handle . IsInvalid && ( data . dwReserved0 & 0x20000000 ) == 0 && ( data . dwReserved0 != IO_REPARSE_TAG_APPEXECLINK ) )
8268+ if ( ( data . dwReserved0 & 0x20000000 ) == 0 && ( data . dwReserved0 != IO_REPARSE_TAG_APPEXECLINK ) )
82348269 {
82358270 return false ;
82368271 }
82378272 }
8238- #endif
8273+
82398274 return true ;
8275+ #endif
82408276 }
82418277
82428278 internal static bool WinIsHardLink ( FileSystemInfo fileInfo )
0 commit comments