mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Use .Net Core File.Delete() method to remove symbolic links and alternate streams (#7017)
.Net Core support processing symbolic links and alternate streams, and thus we can remove our P/Invoke code.
This commit is contained in:
@@ -10,6 +10,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Internal;
|
||||
|
||||
@@ -119,18 +120,9 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
AlternateDataStreamUtilities.DeleteFileStream(path, "Zone.Identifier");
|
||||
}
|
||||
catch (Win32Exception accessException)
|
||||
catch (Exception e)
|
||||
{
|
||||
// NativeErrorCode=2 - File not found.
|
||||
// If the block stream not found the 'path' was not blocked and we successfully return.
|
||||
if (accessException.NativeErrorCode != 2)
|
||||
{
|
||||
WriteError(new ErrorRecord(accessException, "RemoveItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteVerbose(StringUtil.Format(UtilityCommonStrings.NoZoneIdentifierFileStream, path));
|
||||
}
|
||||
WriteError(new ErrorRecord(e, "RemoveItemUnableToAccessFile", ErrorCategory.ResourceUnavailable, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,9 +174,6 @@
|
||||
<data name="TypeNotSupported" xml:space="preserve">
|
||||
<value>'{0}' is not supported in this system.</value>
|
||||
</data>
|
||||
<data name="NoZoneIdentifierFileStream" xml:space="preserve">
|
||||
<value>The file is not blocked: {0}</value>
|
||||
</data>
|
||||
<data name="ConvertToJsonProcessValueVerboseMessage" xml:space="preserve">
|
||||
<value>Processing object of type [{0}] at depth {1}</value>
|
||||
</data>
|
||||
|
||||
@@ -2874,41 +2874,20 @@ namespace Microsoft.PowerShell.Commands
|
||||
continueRemoval = ShouldProcess(directory.FullName, action);
|
||||
}
|
||||
|
||||
if ((directory.Attributes & FileAttributes.ReparsePoint) != 0)
|
||||
if (directory.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
{
|
||||
bool success = InternalSymbolicLinkLinkCodeMethods.DeleteJunction(directory.FullName);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
string error = StringUtil.Format(FileSystemProviderStrings.CannotRemoveItem, directory.FullName);
|
||||
Exception e = new IOException(error);
|
||||
WriteError(new ErrorRecord(e, "DeleteJunctionFailed", ErrorCategory.WriteError, directory));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!Utils.ItemExists(directory.FullName, out bool _))
|
||||
{
|
||||
// Directory does not exist
|
||||
return;
|
||||
}
|
||||
directory.Delete();
|
||||
}
|
||||
catch (Exception accessException)
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(accessException, "RemoveFileSystemItemUnAuthorizedAccess", ErrorCategory.PermissionDenied, directory);
|
||||
|
||||
ErrorDetails errorDetails =
|
||||
new ErrorDetails(this, "FileSystemProviderStrings",
|
||||
"CannotRemoveItem",
|
||||
directory.FullName,
|
||||
accessException.Message);
|
||||
|
||||
errorRecord.ErrorDetails = errorDetails;
|
||||
|
||||
WriteError(errorRecord);
|
||||
return;
|
||||
string error = StringUtil.Format(FileSystemProviderStrings.CannotRemoveItem, directory.FullName);
|
||||
Exception exception = new IOException(error, e);
|
||||
WriteError(new ErrorRecord(exception, "DeleteSymbolicLinkFailed", ErrorCategory.WriteError, directory));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (continueRemoval)
|
||||
@@ -8336,91 +8315,6 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
|
||||
internal static bool DeleteJunction(string junctionPath)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (!String.IsNullOrEmpty(junctionPath))
|
||||
{
|
||||
if (!Platform.IsWindows)
|
||||
{
|
||||
// For non-Windows platform, treat it as a file. Just delete it.
|
||||
try
|
||||
{
|
||||
File.Delete(junctionPath);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
using (SafeHandle handle = OpenReparsePoint(junctionPath, FileDesiredAccess.GenericWrite))
|
||||
{
|
||||
bool success = false;
|
||||
int inOutBufferSize = Marshal.SizeOf<REPARSE_GUID_DATA_BUFFER>();
|
||||
IntPtr outBuffer = Marshal.AllocHGlobal(inOutBufferSize);
|
||||
IntPtr inBuffer = Marshal.AllocHGlobal(inOutBufferSize);
|
||||
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref success);
|
||||
IntPtr dangerousHandle = handle.DangerousGetHandle();
|
||||
int bytesReturned;
|
||||
|
||||
// Do a FSCTL_GET_REPARSE_POINT first because the ReparseTag could be
|
||||
// IO_REPARSE_TAG_MOUNT_POINT or IO_REPARSE_TAG_SYMLINK.
|
||||
// Using the wrong one results in mismatched-tag error.
|
||||
|
||||
REPARSE_GUID_DATA_BUFFER junctionData = new REPARSE_GUID_DATA_BUFFER();
|
||||
Marshal.StructureToPtr<REPARSE_GUID_DATA_BUFFER>(junctionData, outBuffer, false);
|
||||
|
||||
result = DeviceIoControl(dangerousHandle, FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0,
|
||||
outBuffer, inOutBufferSize, out bytesReturned, IntPtr.Zero);
|
||||
if (!result)
|
||||
{
|
||||
int lastError = Marshal.GetLastWin32Error();
|
||||
throw new Win32Exception(lastError);
|
||||
}
|
||||
|
||||
junctionData = Marshal.PtrToStructure<REPARSE_GUID_DATA_BUFFER>(outBuffer);
|
||||
junctionData.ReparseDataLength = 0;
|
||||
junctionData.DataBuffer = new char[MAX_REPARSE_SIZE];
|
||||
|
||||
Marshal.StructureToPtr<REPARSE_GUID_DATA_BUFFER>(junctionData, inBuffer, false);
|
||||
|
||||
// To delete a reparse point:
|
||||
// ReparseDataLength must be 0
|
||||
// inBufferSize must be REPARSE_GUID_DATA_BUFFER_HEADER_SIZE
|
||||
result = DeviceIoControl(dangerousHandle, FSCTL_DELETE_REPARSE_POINT, inBuffer, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);
|
||||
if (!result)
|
||||
{
|
||||
int lastError = Marshal.GetLastWin32Error();
|
||||
throw new Win32Exception(lastError);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(outBuffer);
|
||||
Marshal.FreeHGlobal(inBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException("junctionPath");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static SafeFileHandle OpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode)
|
||||
{
|
||||
#if UNIX
|
||||
@@ -8585,11 +8479,7 @@ namespace System.Management.Automation.Internal
|
||||
}
|
||||
string resultPath = path + adjustedStreamName;
|
||||
|
||||
if (!NativeMethods.DeleteFile(resultPath))
|
||||
{
|
||||
int error = Marshal.GetLastWin32Error();
|
||||
throw new Win32Exception(error);
|
||||
}
|
||||
File.Delete(resultPath);
|
||||
}
|
||||
|
||||
internal static void SetZoneOfOrigin(string path, SecurityZone securityZone)
|
||||
@@ -8617,9 +8507,6 @@ namespace System.Management.Automation.Internal
|
||||
IntPtr lpSecurityAttributes, FileMode dwCreationDisposition,
|
||||
int dwFlagsAndAttributes, IntPtr hTemplateFile);
|
||||
|
||||
[DllImport(PinvokeDllNames.DeleteFileDllName, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern bool DeleteFile(string lpFileName);
|
||||
|
||||
[DllImport(PinvokeDllNames.FindFirstStreamDllName, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "AlternateStreamNativeData.Name")]
|
||||
internal static extern SafeFindHandle FindFirstStreamW(
|
||||
|
||||
@@ -242,6 +242,8 @@ Describe "New-Item with links" -Tags @('CI', 'RequireAdminOnWindows') {
|
||||
|
||||
# Remove the link explicitly to avoid broken symlink issue
|
||||
Remove-Item $FullyQualifiedLink -Force
|
||||
# Test a code path removing a symbolic link (reparse point)
|
||||
Test-Path $FullyQualifiedLink | Should -BeFalse
|
||||
}
|
||||
|
||||
It "New-Item -ItemType SymbolicLink should understand directory path ending with slash" {
|
||||
|
||||
@@ -64,6 +64,6 @@ Describe "Unblock-File" -Tags "CI" {
|
||||
$TestFileCreated = Get-ChildItem $TestFile
|
||||
$TestFileCreated.IsReadOnly | Should -BeTrue
|
||||
|
||||
{ Unblock-File -LiteralPath $TestFile -ErrorAction Stop } | Should -Throw -ErrorId "RemoveItemUnauthorizedAccessError,Microsoft.PowerShell.Commands.UnblockFileCommand"
|
||||
{ Unblock-File -LiteralPath $TestFile -ErrorAction Stop } | Should -Throw -ErrorId "RemoveItemUnableToAccessFile,Microsoft.PowerShell.Commands.UnblockFileCommand"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user