@@ -1891,9 +1891,14 @@ private void Dir(
1891
1891
}
1892
1892
1893
1893
bool hidden = false ;
1894
+ bool checkReparsePoint = true ;
1894
1895
if ( ! Force )
1895
1896
{
1896
1897
hidden = ( recursiveDirectory . Attributes & FileAttributes . Hidden ) != 0 ;
1898
+
1899
+ // We've already taken the expense of initializing the Attributes property here,
1900
+ // so we can use that to avoid needing to call IsReparsePointLikeSymlink() later.
1901
+ checkReparsePoint = recursiveDirectory . Attributes . HasFlag ( FileAttributes . ReparsePoint ) ;
1897
1902
}
1898
1903
1899
1904
// if "Hidden" is explicitly specified anywhere in the attribute filter, then override
@@ -1907,7 +1912,7 @@ private void Dir(
1907
1912
// c) it is not a reparse point with a target (not OneDrive or an AppX link).
1908
1913
if ( tracker == null )
1909
1914
{
1910
- if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( recursiveDirectory ) )
1915
+ if ( checkReparsePoint && InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( recursiveDirectory ) )
1911
1916
{
1912
1917
continue ;
1913
1918
}
@@ -2062,7 +2067,7 @@ public static string NameString(PSObject instance)
2062
2067
{
2063
2068
if ( instance ? . BaseObject is FileSystemInfo fileInfo )
2064
2069
{
2065
- if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( fileInfo ) )
2070
+ if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( fileInfo ) )
2066
2071
{
2067
2072
return $ "{ PSStyle . Instance . FileInfo . SymbolicLink } { fileInfo . Name } { PSStyle . Instance . Reset } -> { InternalSymbolicLinkLinkCodeMethods . GetTarget ( instance ) } ";
2068
2073
}
@@ -2090,7 +2095,7 @@ public static string NameString(PSObject instance)
2090
2095
else
2091
2096
{
2092
2097
return instance ? . BaseObject is FileSystemInfo fileInfo
2093
- ? InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( fileInfo )
2098
+ ? InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( fileInfo )
2094
2099
? $ "{ fileInfo . Name } -> { InternalSymbolicLinkLinkCodeMethods . GetTarget ( instance ) } "
2095
2100
: fileInfo . Name
2096
2101
: string . Empty ;
@@ -3131,22 +3136,31 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool
3131
3136
continueRemoval = ShouldProcess ( directory . FullName , action ) ;
3132
3137
}
3133
3138
3134
- if ( directory . Attributes . HasFlag ( FileAttributes . ReparsePoint ) )
3139
+ if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( directory ) )
3135
3140
{
3141
+ void WriteErrorHelper ( Exception exception )
3142
+ {
3143
+ WriteError ( new ErrorRecord ( exception , errorId : "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ) ) ;
3144
+ }
3145
+
3136
3146
try
3137
3147
{
3138
- // TODO:
3139
- // Different symlinks seem to vary by behavior.
3140
- // In particular, OneDrive symlinks won't remove without recurse,
3141
- // but the .NET API here does not allow us to distinguish them.
3142
- // We may need to revisit using p/Invokes here to get the right behavior
3143
- directory . Delete ( ) ;
3148
+ if ( InternalTestHooks . OneDriveTestOn )
3149
+ {
3150
+ WriteErrorHelper ( new IOException ( ) ) ;
3151
+ return ;
3152
+ }
3153
+ else
3154
+ {
3155
+ // Name surrogates should just be detached.
3156
+ directory . Delete ( ) ;
3157
+ }
3144
3158
}
3145
3159
catch ( Exception e )
3146
3160
{
3147
3161
string error = StringUtil . Format ( FileSystemProviderStrings . CannotRemoveItem , directory . FullName , e . Message ) ;
3148
3162
var exception = new IOException ( error , e ) ;
3149
- WriteError ( new ErrorRecord ( exception , errorId : "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ) ) ;
3163
+ WriteErrorHelper ( exception ) ;
3150
3164
}
3151
3165
3152
3166
return ;
@@ -8056,8 +8070,7 @@ protected override bool ReleaseHandle()
8056
8070
private static extern bool FindClose ( IntPtr handle ) ;
8057
8071
}
8058
8072
8059
- // SetLastError is false as the use of this API doesn't not require GetLastError() to be called
8060
- [ DllImport ( PinvokeDllNames . FindFirstFileDllName , EntryPoint = "FindFirstFileExW" , SetLastError = false , CharSet = CharSet . Unicode ) ]
8073
+ [ DllImport ( PinvokeDllNames . FindFirstFileDllName , EntryPoint = "FindFirstFileExW" , SetLastError = true , CharSet = CharSet . Unicode ) ]
8061
8074
private static extern SafeFindHandle FindFirstFileEx ( string lpFileName , FINDEX_INFO_LEVELS fInfoLevelId , ref WIN32_FIND_DATA lpFindFileData , FINDEX_SEARCH_OPS fSearchOp , IntPtr lpSearchFilter , int dwAdditionalFlags ) ;
8062
8075
8063
8076
internal enum FINDEX_INFO_LEVELS : uint
@@ -8248,28 +8261,55 @@ internal static bool IsReparsePoint(FileSystemInfo fileInfo)
8248
8261
return fileInfo . Attributes . HasFlag ( System . IO . FileAttributes . ReparsePoint ) ;
8249
8262
}
8250
8263
8251
- internal static bool IsReparsePointWithTarget ( FileSystemInfo fileInfo )
8264
+ internal static bool IsReparsePointLikeSymlink ( FileSystemInfo fileInfo )
8252
8265
{
8253
- if ( ! IsReparsePoint ( fileInfo ) )
8266
+ #if UNIX
8267
+ // Reparse point on Unix is a symlink.
8268
+ return IsReparsePoint ( fileInfo ) ;
8269
+ #else
8270
+ if ( InternalTestHooks . OneDriveTestOn && fileInfo . Name == InternalTestHooks . OneDriveTestSymlinkName )
8254
8271
{
8255
- return false ;
8272
+ return ! InternalTestHooks . OneDriveTestRecurseOn ;
8256
8273
}
8257
- #if ! UNIX
8258
- // It is a reparse point and we should check some reparse point tags.
8259
- var data = new WIN32_FIND_DATA ( ) ;
8260
- using ( var handle = FindFirstFileEx ( fileInfo . FullName , FINDEX_INFO_LEVELS . FindExInfoBasic , ref data , FINDEX_SEARCH_OPS . FindExSearchNameMatch , IntPtr . Zero , 0 ) )
8274
+
8275
+ WIN32_FIND_DATA data = default ;
8276
+ string fullPath = Path . TrimEndingDirectorySeparator ( fileInfo . FullName ) ;
8277
+ if ( fullPath . Length > MAX_PATH )
8278
+ {
8279
+ fullPath = PathUtils . EnsureExtendedPrefix ( fullPath ) ;
8280
+ }
8281
+
8282
+ using ( SafeFindHandle handle = FindFirstFileEx ( fullPath , FINDEX_INFO_LEVELS . FindExInfoBasic , ref data , FINDEX_SEARCH_OPS . FindExSearchNameMatch , IntPtr . Zero , 0 ) )
8261
8283
{
8284
+ if ( handle . IsInvalid )
8285
+ {
8286
+ // Our handle could be invalidated by something else touching the filesystem,
8287
+ // so ensure we deal with that possibility here
8288
+ int lastError = Marshal . GetLastWin32Error ( ) ;
8289
+ throw new Win32Exception ( lastError ) ;
8290
+ }
8291
+
8292
+ // We already have the file attribute information from our Win32 call,
8293
+ // so no need to take the expense of the FileInfo.FileAttributes call
8294
+ const int FILE_ATTRIBUTE_REPARSE_POINT = 0x0400 ;
8295
+ if ( ( data . dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) == 0 )
8296
+ {
8297
+ // Not a reparse point.
8298
+ return false ;
8299
+ }
8300
+
8262
8301
// The name surrogate bit 0x20000000 is defined in https://docs.microsoft.com/windows/win32/fileio/reparse-point-tags
8263
8302
// Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem
8264
8303
// (like symlinks and mount points).
8265
8304
// In the case of OneDrive, they are not name surrogates and would be safe to recurse into.
8266
- if ( ! handle . IsInvalid && ( data . dwReserved0 & 0x20000000 ) == 0 && ( data . dwReserved0 != IO_REPARSE_TAG_APPEXECLINK ) )
8305
1241
code>
+ if ( ( data . dwReserved0 & 0x20000000 ) == 0 && ( data . dwReserved0 != IO_REPARSE_TAG_APPEXECLINK ) )
8267
8306
{
8268
8307
return false ;
8269
8308
}
8270
8309
}
8271
- #endif
8310
+
8272
8311
return true ;
8312
+ #endif
8273
8313
}
8274
8314
8275
8315
internal static bool WinIsHardLink ( FileSystemInfo fileInfo )
0 commit comments