Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ private static void SpinUpBreakHandlerThread(bool shouldEndSession)
host.ShouldEndSession = shouldEndSession;
}

// Creation of the tread and starting it should be an atomic operation.
// Creation of the thread and starting it should be an atomic operation.
// otherwise the code in Run method can get instance of the breakhandlerThread
// after it is created and before started and call join on it. This will result
// in ThreadStateException.
Expand Down
5 changes: 0 additions & 5 deletions src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -516,11 +516,6 @@ internal static bool NonWindowsIsHardLink(FileSystemInfo fileInfo)
return Unix.IsHardLink(fileInfo);
}

internal static string NonWindowsInternalGetTarget(string path)
{
return Unix.NativeMethods.FollowSymLink(path);
}

internal static string NonWindowsGetUserFromPid(int path)
{
return Unix.NativeMethods.GetUserFromPid(path);
Expand Down
20 changes: 13 additions & 7 deletions src/System.Management.Automation/engine/NativeCommandProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1101,15 +1101,21 @@ private static bool IsWindowsApplication(string fileName)
#if UNIX
return false;
#else
if (!Platform.IsWindowsDesktop) { return false; }
if (!Platform.IsWindowsDesktop)
{
return false;
}

// SHGetFileInfo() does not understand reparse points and returns 0 ("non exe or error")
// so we are trying to get a real path before.
// It is a workaround for Microsoft Store applications.
string realPath = Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods.WinInternalGetTarget(fileName);
if (realPath is not null)
// The function 'SHGetFileInfo()' does not understand reparse points and returns 0 ("non exe or error")
// for a symbolic link file, so we try to get the immediate link target in that case.
// Why not get the final target (use 'returnFinalTarget: true')? Because:
// 1. When starting a process on Windows, if the 'FileName' is a symbolic link, the immediate link target will automatically be used,
// but the OS does not do recursive resolution when the immediate link target is also a symbolic link.
// 2. Keep the same behavior as before adopting the 'LinkTarget' and 'ResolveLinkTarget' APIs in .NET 6.
string linkTarget = File.ResolveLinkTarget(fileName, returnFinalTarget: false)?.FullName;
if (linkTarget is not null)
{
fileName = realPath;
fileName = linkTarget;
}

SHFILEINFO shinfo = new SHFILEINFO();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,10 +680,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag<string> errors)
AddMember(
errors,
typeName,
new PSCodeProperty(
@"Target",
GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), @"GetTarget"),
setterCodeReference: null),
new PSAliasProperty(@"Target", @"LinkTarget", conversionType: null),
typeMembers,
isOverride: false);

Expand Down Expand Up @@ -808,10 +805,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag<string> errors)
AddMember(
errors,
typeName,
new PSCodeProperty(
@"Target",
GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), @"GetTarget"),
setterCodeReference: null),
new PSAliasProperty(@"Target", @"LinkTarget", conversionType: null),
typeMembers,
isOverride: false);

Expand Down
227 changes: 56 additions & 171 deletions src/System.Management.Automation/namespaces/FileSystemProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2069,7 +2069,7 @@ public static string NameString(PSObject instance)
{
if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointLikeSymlink(fileInfo))
{
return $"{PSStyle.Instance.FileInfo.SymbolicLink}{fileInfo.Name}{PSStyle.Instance.Reset} -> {InternalSymbolicLinkLinkCodeMethods.GetTarget(instance)}";
return $"{PSStyle.Instance.FileInfo.SymbolicLink}{fileInfo.Name}{PSStyle.Instance.Reset} -> {fileInfo.LinkTarget}";
}
else if (fileInfo.Attributes.HasFlag(FileAttributes.Directory))
{
Expand All @@ -2096,7 +2096,7 @@ public static string NameString(PSObject instance)
{
return instance?.BaseObject is FileSystemInfo fileInfo
? InternalSymbolicLinkLinkCodeMethods.IsReparsePointLikeSymlink(fileInfo)
? $"{fileInfo.Name} -> {InternalSymbolicLinkLinkCodeMethods.GetTarget(instance)}"
? $"{fileInfo.Name} -> {fileInfo.LinkTarget}"
: fileInfo.Name
: string.Empty;
}
Expand Down Expand Up @@ -8105,15 +8105,18 @@ internal unsafe struct WIN32_FIND_DATA
/// </summary>
/// <param name="instance">The object of FileInfo or DirectoryInfo type.</param>
/// <returns>The target of the reparse point.</returns>
[Obsolete("This method is now obsolete. Please use the .NET API 'FileSystemInfo.LinkTarget'", error: true)]
public static string GetTarget(PSObject instance)
{
if (instance.BaseObject is FileSystemInfo fileSysInfo)
{
#if !UNIX
return WinInternalGetTarget(fileSysInfo.FullName);
#else
return UnixInternalGetTarget(fileSysInfo.FullName);
#endif
if (!fileSysInfo.Exists)
{
throw new ArgumentException(
StringUtil.Format(SessionStateStrings.PathNotFound, fileSysInfo.FullName));
}

return fileSysInfo.LinkTarget;
}

return null;
Expand All @@ -8136,20 +8139,6 @@ public static string GetLinkType(PSObject instance)
return null;
}

#if UNIX
private static string UnixInternalGetTarget(string filePath)
{
string link = Platform.NonWindowsInternalGetTarget(filePath);

if (string.IsNullOrEmpty(link))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}

return link;
}
#endif

private static string InternalGetLinkType(FileSystemInfo fileInfo)
{
if (Platform.IsWindows)
Expand All @@ -8165,16 +8154,11 @@ private static string InternalGetLinkType(FileSystemInfo fileInfo)
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
private static string WinInternalGetLinkType(string filePath)
{
if (!Platform.IsWindows)
{
throw new PlatformNotSupportedException();
}

// We set accessMode parameter to zero because documentation says:
// If this parameter is zero, the application can query certain metadata
// such as file, directory, or device attributes without accessing
// that file or device, even if GENERIC_READ access would have been denied.
using (SafeFileHandle handle = OpenReparsePoint(filePath, FileDesiredAccess.GenericZero))
using (SafeFileHandle handle = WinOpenReparsePoint(filePath, FileDesiredAccess.GenericZero))
{
int outBufferSize = Marshal.SizeOf<REPARSE_DATA_BUFFER_SYMBOLICLINK>();

Expand Down Expand Up @@ -8439,176 +8423,77 @@ internal static bool WinIsHardLink(ref IntPtr handle)
return succeeded && (handleInfo.NumberOfLinks > 1);
}

#if !UNIX
internal static string WinInternalGetTarget(string path)
{
// We set accessMode parameter to zero because documentation says:
// If this parameter is zero, the application can query certain metadata
// such as file, directory, or device attributes without accessing
// that file or device, even if GENERIC_READ access would have been denied.
using (SafeFileHandle handle = OpenReparsePoint(path, FileDesiredAccess.GenericZero))
{
return WinInternalGetTarget(handle);
}
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
private static string WinInternalGetTarget(SafeFileHandle handle)
{
int outBufferSize = Marshal.SizeOf<REPARSE_DATA_BUFFER_SYMBOLICLINK>();

IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize);
bool success = false;

try
{
int bytesReturned;

// OACR warning 62001 about using DeviceIOControl has been disabled.
// According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used.
handle.DangerousAddRef(ref success);

bool result = DeviceIoControl(
handle.DangerousGetHandle(),
FSCTL_GET_REPARSE_POINT,
InBuffer: IntPtr.Zero,
nInBufferSize: 0,
outBuffer,
outBufferSize,
out bytesReturned,
lpOverlapped: IntPtr.Zero);

if (!result)
{
// It's not a reparse point or the file system doesn't support reparse points.
return null;
}

string targetDir = null;

REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_SYMBOLICLINK>(outBuffer);

switch (reparseDataBuffer.ReparseTag)
{
case IO_REPARSE_TAG_SYMLINK:
targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength);
break;

case IO_REPARSE_TAG_MOUNT_POINT:
REPARSE_DATA_BUFFER_MOUNTPOINT reparseMountPointDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_MOUNTPOINT>(outBuffer);
targetDir = Encoding.Unicode.GetString(reparseMountPointDataBuffer.PathBuffer, reparseMountPointDataBuffer.SubstituteNameOffset, reparseMountPointDataBuffer.SubstituteNameLength);
break;

default:
return null;
}

if (targetDir != null && targetDir.StartsWith(NonInterpretedPathPrefix, StringComparison.OrdinalIgnoreCase))
{
targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length);
}

return targetDir;
}
finally
{
if (success)
{
handle.DangerousRelease();
}

Marshal.FreeHGlobal(outBuffer);
}
}
#endif

internal static bool CreateJunction(string path, string target)
{
// this is a purely Windows specific feature, no feature flag
// used for that reason
// this is a purely Windows specific feature, no feature flag used for that reason.
if (Platform.IsWindows)
{
return WinCreateJunction(path, target);
}
else
{
return false;
}

return false;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
private static bool WinCreateJunction(string path, string target)
{
if (!string.IsNullOrEmpty(path))
if (string.IsNullOrEmpty(path))
{
if (!string.IsNullOrEmpty(target))
{
using (SafeHandle handle = OpenReparsePoint(path, FileDesiredAccess.GenericWrite))
{
byte[] mountPointBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(target));
throw new ArgumentNullException(nameof(path));
}

REPARSE_DATA_BUFFER_MOUNTPOINT mountPoint = new REPARSE_DATA_BUFFER_MOUNTPOINT();
mountPoint.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
mountPoint.ReparseDataLength = (ushort)(mountPointBytes.Length + 12); // Added space for the header and null endo
mountPoint.SubstituteNameOffset = 0;
mountPoint.SubstituteNameLength = (ushort)mountPointBytes.Length;
mountPoint.PrintNameOffset = (ushort)(mountPointBytes.Length + 2); // 2 as unicode null take 2 bytes.
mountPoint.PrintNameLength = 0;
mountPoint.PathBuffer = new byte[0x3FF0]; // Buffer for max size.
Array.Copy(mountPointBytes, mountPoint.PathBuffer, mountPointBytes.Length);
if (string.IsNullOrEmpty(target))
{
throw new ArgumentNullException(nameof(target));
}

int nativeBufferSize = Marshal.SizeOf(mountPoint);
IntPtr nativeBuffer = Marshal.AllocHGlobal(nativeBufferSize);
bool success = false;
using (SafeHandle handle = WinOpenReparsePoint(path, FileDesiredAccess.GenericWrite))
{
byte[] mountPointBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(target));

try
{
Marshal.StructureToPtr(mountPoint, nativeBuffer, false);
var mountPoint = new REPARSE_DATA_BUFFER_MOUNTPOINT();
mountPoint.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
mountPoint.ReparseDataLength = (ushort)(mountPointBytes.Length + 12); // Added space for the header and null endo
mountPoint.SubstituteNameOffset = 0;
mountPoint.SubstituteNameLength = (ushort)mountPointBytes.Length;
mountPoint.PrintNameOffset = (ushort)(mountPointBytes.Length + 2); // 2 as unicode null take 2 bytes.
mountPoint.PrintNameLength = 0;
mountPoint.PathBuffer = new byte[0x3FF0]; // Buffer for max size.
Array.Copy(mountPointBytes, mountPoint.PathBuffer, mountPointBytes.Length);

int bytesReturned = 0;
int nativeBufferSize = Marshal.SizeOf(mountPoint);
IntPtr nativeBuffer = Marshal.AllocHGlobal(nativeBufferSize);
bool success = false;

// OACR warning 62001 about using DeviceIOControl has been disabled.
// According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used.
handle.DangerousAddRef(ref success);
try
{
Marshal.StructureToPtr(mountPoint, nativeBuffer, false);

bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, nativeBuffer, mountPointBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);
int bytesReturned = 0;

if (!result)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// OACR warning 62001 about using DeviceIOControl has been disabled.
// According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used.
handle.DangerousAddRef(ref success);

return result;
}
finally
{
Marshal.FreeHGlobal(nativeBuffer);
bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, nativeBuffer, mountPointBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);

if (success)
{
handle.DangerousRelease();
}
}
if (!result)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}

return result;
}
else
finally
{
throw new ArgumentNullException(nameof(target));
Marshal.FreeHGlobal(nativeBuffer);

if (success)
{
handle.DangerousRelease();
}
}
}
else
{
throw new ArgumentNullException(nameof(path));
}
}

private static SafeFileHandle OpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode)
{
#if UNIX
throw new PlatformNotSupportedException();
#else
return WinOpenReparsePoint(reparsePoint, accessMode);
#endif
}

private static SafeFileHandle WinOpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode)
Expand Down
Loading