@@ -1890,9 +1890,17 @@ private void Dir(
1890
1890
}
1891
1891
1892
1892
bool hidden = false ;
1893
+ bool checkReparsePoint = true ;
1893
1894
if ( ! Force )
1894
1895
{
1895
1896
hidden = ( recursiveDirectory . Attributes & FileAttributes . Hidden ) != 0 ;
6D40
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 ;
1896
1904
}
1897
1905
1898
1906
// if "Hidden" is explicitly specified anywhere in the attribute filter, then override
@@ -1906,7 +1914,7 @@ private void Dir(
1906
1914
// c) it is not a reparse point with a target (not OneDrive or an AppX link).
1907
1915
if ( tracker == null )
1908
1916
{
1909
- if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( recursiveDirectory ) )
1917
+ if ( checkReparsePoint && InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( recursiveDirectory ) )
1910
1918
{
1911
1919
continue ;
1912
1920
}
@@ -2058,7 +2066,7 @@ string ToModeString(FileSystemInfo fileSystemInfo)
2058
2066
public static string NameString ( PSObject instance )
2059
2067
{
2060
2068
return instance ? . BaseObject is FileSystemInfo fileInfo
2061
- ? InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( fileInfo )
2069
+ ? InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( fileInfo )
2062
2070
? $ "{ fileInfo . Name } -> { InternalSymbolicLinkLinkCodeMethods . GetTarget ( instance ) } "
2063
2071
: fileInfo . Name
2064
2072
: string . Empty ;
@@ -3098,22 +3106,31 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool
3098
3106
continueRemoval = ShouldProcess ( directory . FullName , action ) ;
3099
3107
}
3100
3108
3101
- if ( directory . Attributes . HasFlag ( FileAttributes . ReparsePoint ) )
3109
+ if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( directory ) )
3102
3110
{
3111
+ void WriteErrorHelper ( Exception exception )
3112
+ {
3113
+ WriteError ( new ErrorRecord ( exception , errorId : "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ) ) ;
3114
+ }
3115
+
3103
3116
try
3104
3117
{
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
+ }
3111
3128
}
3112
3129
catch ( Exception e )
3113
3130
{
3114
3131
string error = StringUtil . Format ( FileSystemProviderStrings . CannotRemoveItem , directory . FullName , e . Message ) ;
3115
3132
var exception = new IOException ( error , e ) ;
3116
- WriteError ( new ErrorRecord ( exception , errorId : "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ) ) ;
3133
+ WriteErrorHelper ( exception ) ;
3117
3134
}
3118
3135
3119
3136
return ;
@@ -8215,28 +8232,47 @@ internal static bool IsReparsePoint(FileSystemInfo fileInfo)
8215
8232
return fileInfo . Attributes . HasFlag ( System . IO . FileAttributes . ReparsePoint ) ;
8216
8233
}
8217
8234
8218
- internal static bool IsReparsePointWithTarget ( FileSystemInfo fileInfo )
8235
+ internal static bool IsReparsePointLikeSymlink ( FileSystemInfo fileInfo )
8219
8236
{
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 )
8221
8242
{
8222
- return false ;
8243
+ return ! InternalTestHooks . OneDriveTestRecurseOn ;
8223
8244
}
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 ;
8227
8247
using ( var handle = FindFirstFileEx ( fileInfo . FullName , FINDEX_INFO_LEVELS . FindExInfoBasic , ref data , FINDEX_SEARCH_OPS . FindExSearchNameMatch , IntPtr . Zero , 0 ) )
8228
8248
{
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
+
8229
8264
// The name surrogate bit 0x20000000 is defined in https://docs.microsoft.com/windows/win32/fileio/reparse-point-tags
8230
8265
// Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem
8231
8266
// (like symlinks and mount points).
8232
8267
// 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 ) )
8234
8269
{
8235
8270
return false ;
8236
8271
}
8237
8272
}
8238
- #endif
8273
+
8239
8274
return true ;
8275
+ #endif
8240
8276
}
8241
8277
8242
8278
internal static bool WinIsHardLink ( FileSystemInfo fileInfo )
0 commit comments