From 0a3b106a70204723df0b051f64dfc0680f785e40 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 27 Sep 2021 10:32:45 -0700 Subject: [PATCH 1/7] Fix typo --- src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index b0ae4735026..c2206ac7de8 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -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. From 84d79c56c2a6f1973673c733aaf024182851f174 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 27 Sep 2021 17:36:46 -0700 Subject: [PATCH 2/7] Make 'Target' an alias property to 'LinkTarget' --- .../CoreCLR/CorePsPlatform.cs | 5 - .../engine/NativeCommandProcessor.cs | 26 +- .../engine/TypeTable_Types_Ps1Xml.cs | 10 +- .../namespaces/FileSystemProvider.cs | 226 +++++------------- 4 files changed, 72 insertions(+), 195 deletions(-) diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index 884e91e3356..ce37ae12d17 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -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); diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index 7df3dd82546..b9b4fc923ec 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -1098,18 +1098,23 @@ private static void KillChildProcesses(int parentId, ProcessWithParentId[] curre [ArchitectureSensitive] private static bool IsWindowsApplication(string fileName) { -#if UNIX - return false; -#else - if (!Platform.IsWindowsDesktop) { return false; } + var fileInfo = new FileInfo(fileName); + if (!fileInfo.Exists) + { + throw new ArgumentException( + StringUtil.Format(SessionStateStrings.PathNotFound, fileName)); + } - // 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) + if (!Platform.IsWindowsDesktop) { - fileName = realPath; + return false; + } + + // SHGetFileInfo() does not understand reparse points and returns 0 ("non exe or error"), so we use the link target in that case. + // This is a workaround for Microsoft Store applications. + if (fileInfo.LinkTarget is not null) + { + fileName = fileInfo.LinkTarget; } SHFILEINFO shinfo = new SHFILEINFO(); @@ -1130,7 +1135,6 @@ private static bool IsWindowsApplication(string fileName) // anything else - is a windows program... return true; } -#endif } #endregion checkForConsoleApplication diff --git a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs index 7113c0953cb..648bafe152c 100644 --- a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs +++ b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs @@ -680,10 +680,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag 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); @@ -808,10 +805,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag 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); diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 34c399f7bed..26dbff43434 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -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)) { @@ -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; } @@ -8109,11 +8109,13 @@ 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; @@ -8136,20 +8138,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) @@ -8165,16 +8153,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(); @@ -8439,176 +8422,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(); - - 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(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(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) From ea7a236a85a5102f1ae2119947989256a9f2f5db Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 28 Sep 2021 13:10:24 -0700 Subject: [PATCH 3/7] Add obsolete attribute to 'GetTarget' method --- .../namespaces/FileSystemProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 26dbff43434..7e6d302b22a 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -8105,6 +8105,7 @@ internal unsafe struct WIN32_FIND_DATA /// /// The object of FileInfo or DirectoryInfo type. /// The target of the reparse point. + [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) From 7b5297bb159ea5c33a4c0cffa10257ffd33eae74 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 28 Sep 2021 15:47:36 -0700 Subject: [PATCH 4/7] Use ResolveLinkTarget --- .../engine/NativeCommandProcessor.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index b9b4fc923ec..23fdd8104d0 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -1098,23 +1098,24 @@ private static void KillChildProcesses(int parentId, ProcessWithParentId[] curre [ArchitectureSensitive] private static bool IsWindowsApplication(string fileName) { - var fileInfo = new FileInfo(fileName); - if (!fileInfo.Exists) - { - throw new ArgumentException( - StringUtil.Format(SessionStateStrings.PathNotFound, fileName)); - } - +#if UNIX + return false; +#else if (!Platform.IsWindowsDesktop) { return false; } - // SHGetFileInfo() does not understand reparse points and returns 0 ("non exe or error"), so we use the link target in that case. - // This is a workaround for Microsoft Store applications. - if (fileInfo.LinkTarget 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 = new FileInfo(fileName).ResolveLinkTarget(returnFinalTarget: false)?.FullName; + if (linkTarget is not null) { - fileName = fileInfo.LinkTarget; + fileName = linkTarget; } SHFILEINFO shinfo = new SHFILEINFO(); @@ -1135,6 +1136,7 @@ private static bool IsWindowsApplication(string fileName) // anything else - is a windows program... return true; } +#endif } #endregion checkForConsoleApplication From 898b104c7331e51d70a4611f501a0a2a8865e697 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 29 Sep 2021 12:28:11 -0700 Subject: [PATCH 5/7] Use 'File.ResolveLinkTarget' --- .../engine/NativeCommandProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index 23fdd8104d0..2c5efb35668 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -1112,7 +1112,7 @@ private static bool IsWindowsApplication(string fileName) // 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 = new FileInfo(fileName).ResolveLinkTarget(returnFinalTarget: false)?.FullName; + string linkTarget = File.ResolveLinkTarget(fileName, returnFinalTarget: false)?.FullName; if (linkTarget is not null) { fileName = linkTarget; From c0be25112fb1a32aa85298d0cd1c7309e04e38e7 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Fri, 1 Oct 2021 13:41:01 -0700 Subject: [PATCH 6/7] Add test to cover the immediate target resolution --- .../FileSystem.Tests.ps1 | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 index e5d150013cd..3181603d044 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 @@ -773,6 +773,42 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" $childB.Name | Should -BeExactly $childA.Name } } + + Context "Show immediate target" { + BeforeAll { + $testDir = Join-Path $TestDrive "immediate-target" + New-Item -ItemType Directory $testDir > $null + + $testFile = Join-Path $testDir "target" + Set-Content -Path $testFile -Value "Hello world" + + Push-Location $testDir + New-Item -ItemType SymbolicLink -Path firstLink -Value target > $null + New-Item -ItemType SymbolicLink -Path secondLink -Value firstLink > $null + Pop-Location + } + + AfterAll { + Remove-Item $testDir -Recurse -Force + } + + It "Property 'Target' should show the immediate target" { + $firstLink = Get-Item (Join-Path $testDir 'firstLink') + $firstLink.Target | Should -BeExactly 'target' + $str = [Microsoft.PowerShell.Commands.FileSystemProvider]::NameString($firstLink) + [System.Management.Automation.Internal.StringDecorated]::new($str).ToString([System.Management.Automation.OutputRendering]::PlainText) | Should -BeExactly 'firstLink -> target' + + $secondLink = Get-Item (Join-Path $testDir 'secondLink') + $secondLink.Target | Should -BeExactly 'firstLink' + $str = [Microsoft.PowerShell.Commands.FileSystemProvider]::NameString($secondLink) + [System.Management.Automation.Internal.StringDecorated]::new($str).ToString([System.Management.Automation.OutputRendering]::PlainText) | Should -BeExactly 'secondLink -> firstLink' + } + + It "Get-Content should be able to resolve the final target" { + Get-Content (Join-Path $testDir 'firstLink') | Should -BeExactly "Hello world" + Get-Content (Join-Path $testDir 'secondLink') | Should -BeExactly "Hello world" + } + } } Describe "Copy-Item can avoid copying an item onto itself" -Tags "CI", "RequireAdminOnWindows" { From 6a8e3ad7d2c295f9f75c4ece586668cc4f00db0d Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 4 Oct 2021 09:45:50 -0700 Subject: [PATCH 7/7] Address Ilya's comment --- .../Microsoft.PowerShell.Management/FileSystem.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 index 3181603d044..10ec92b6be6 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 @@ -783,8 +783,8 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" Set-Content -Path $testFile -Value "Hello world" Push-Location $testDir - New-Item -ItemType SymbolicLink -Path firstLink -Value target > $null - New-Item -ItemType SymbolicLink -Path secondLink -Value firstLink > $null + New-Item -ItemType SymbolicLink -Path 'firstLink' -Value 'target' > $null + New-Item -ItemType SymbolicLink -Path 'secondLink' -Value 'firstLink' > $null Pop-Location }