@@ -1890,9 +1890,17 @@ 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+ // Performance optimization.
1899+ // Since we have already checked Attributes for Hidden we have already did a p/invoke
1900+ // and initialized Attributes property.
1901+ // So here we can check for ReparsePoint without new p/invoke.
1902+ // If it is not a reparse point we skip one p/invoke in IsReparsePointLikeSymlink() below.
1903+ checkReparsePoint = ( recursiveDirectory . Attributes & FileAttributes . ReparsePoint ) != 0 ;
18961904 }
18971905
18981906 // if "Hidden" is explicitly specified anywhere in the attribute filter, then override
@@ -1906,7 +1914,7 @@ private void Dir(
19061914 // c) it is not a reparse point with a target (not OneDrive or an AppX link).
19071915 if ( tracker == null )
19081916 {
1909- if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( recursiveDirectory ) )
1917+ if ( checkReparsePoint && InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( recursiveDirectory ) )
19101918 {
19111919 continue ;
19121920 }
@@ -2058,7 +2066,7 @@ string ToModeString(FileSystemInfo fileSystemInfo)
20582066 public static string NameString ( PSObject instance )
20592067 {
20602068 return instance ? . BaseObject is FileSystemInfo fileInfo
2061- ? InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( fileInfo )
2069+ ? InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( fileInfo )
20622070 ? $ "{ fileInfo . Name } -> { InternalSymbolicLinkLinkCodeMethods . GetTarget ( instance ) } "
20632071 : fileInfo . Name
20642072 : string . Empty ;
@@ -3098,22 +3106,31 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool
30983106 continueRemoval = ShouldProcess ( directory . FullName , action ) ;
30993107 }
31003108
3101- if ( directory . Attributes . HasFlag ( FileAttributes . ReparsePoint ) )
3109+ if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( directory ) )
31023110 {
3111+ void WriteErrorHelper ( Exception exception )
3112+ {
3113+ WriteError ( new ErrorRecord ( exception , errorId : "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ) ) ;
3114+ }
3115+
31033116 try
31043117 {
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 ( ) ;
3118+ if ( InternalTestHooks . OneDriveTestOn )
3119+ {
3120+ WriteErrorHelper ( new IOException ( ) ) ;
3121+ return ;
3122+ }
3123+ else
3124+ {
3125+ // Name surrogates should just be detached.
3126+ directory . Delete ( ) ;
3127+ }
31113128 }
31123129 catch ( Exception e )
31133130 {
31143131 string error = StringUtil . Format ( FileSystemProviderStrings . CannotRemoveItem , directory . FullName , e . Message ) ;
31153132 var exception = new IOException ( error , e ) ;
3116- WriteError ( new ErrorRecord ( exception , errorId : "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ) ) ;
3133+ WriteErrorHelper ( exception ) ;
31173134 }
31183135
31193136 return ;
@@ -8215,28 +8232,47 @@ internal static bool IsReparsePoint(FileSystemInfo fileInfo)
82158232 return fileInfo . Attributes . HasFlag ( System . IO . FileAttributes . ReparsePoint ) ;
82168233 }
82178234
8218- internal static bool IsReparsePointWithTarget ( FileSystemInfo fileInfo )
8235+ internal static bool IsReparsePointLikeSymlink ( FileSystemInfo fileInfo )
82198236 {
8220- if ( ! IsReparsePoint ( fileInfo ) )
8237+ #if UNIX
8238+ // Reparse point on Unix is a symlink.
8239+ return IsReparsePoint ( fileInfo ) ;
8240+ #else
8241+ if ( InternalTestHooks . OneDriveTestOn && fileInfo . Name == InternalTestHooks . OneDriveTestSymlinkName )
82218242 {
8222- return false ;
8243+ return ! InternalTestHooks . OneDriveTestRecurseOn ;
82238244 }
8224- #if ! UNIX
8225- // It is a reparse point and we should check some reparse point tags.
8226- var data = new WIN32_FIND_DATA ( ) ;
8245+
8246+ WIN32_FIND_DATA data = default ;
82278247 using ( var handle = FindFirstFileEx ( fileInfo . FullName , FINDEX_INFO_LEVELS . FindExInfoBasic , ref data , FINDEX_SEARCH_OPS . FindExSearchNameMatch , IntPtr . Zero , 0 ) )
82288248 {
8249+ if ( handle . IsInvalid )
8250+ {
8251+ // If we can not open the file object we assume it's a symlink.
8252+ return true ;
8253+ }
8254+
8255+ // To exclude one extra p/invoke in some scenarios
8256+ // we don't check fileInfo.FileAttributes
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