diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 467526a015..4674fe3327 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -4764,7 +4764,7 @@ namespace System.Management.Automation ref resumeHandle); var shares = new List(); - if (result == Interop.Windows.NERR_Success || result == Interop.Windows.ERROR_MORE_DATA) + if (result == Interop.Windows.ERROR_SUCCESS || result == Interop.Windows.ERROR_MORE_DATA) { for (int i = 0; i < numEntries; ++i) { diff --git a/src/System.Management.Automation/engine/Interop/Windows/Errors.cs b/src/System.Management.Automation/engine/Interop/Windows/Errors.cs new file mode 100644 index 0000000000..d165493321 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/Errors.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +internal static partial class Interop +{ + internal static partial class Windows + { + // List of error constants https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes + internal const int ERROR_SUCCESS = 0; + internal const int ERROR_GEN_FAILURE = 31; + internal const int ERROR_NOT_SUPPORTED = 50; + internal const int ERROR_NO_NETWORK = 1222; + internal const int ERROR_MORE_DATA = 234; + internal const int ERROR_CONNECTION_UNAVAIL = 1201; + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/NetShareEnum.cs b/src/System.Management.Automation/engine/Interop/Windows/NetShareEnum.cs index c3b430dec1..7efad887f1 100644 --- a/src/System.Management.Automation/engine/Interop/Windows/NetShareEnum.cs +++ b/src/System.Management.Automation/engine/Interop/Windows/NetShareEnum.cs @@ -10,8 +10,6 @@ internal static partial class Interop internal static unsafe partial class Windows { internal const int MAX_PREFERRED_LENGTH = -1; - internal const int NERR_Success = 0; - internal const int ERROR_MORE_DATA = 234; internal const int STYPE_DISKTREE = 0; internal const int STYPE_MASK = 0x000000FF; diff --git a/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs b/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs new file mode 100644 index 0000000000..cf6685e4dd --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static unsafe partial class Windows + { + private static bool s_WNetApiNotAvailable; + + [LibraryImport("mpr.dll", EntryPoint = "WNetGetConnectionW")] + internal static partial int WNetGetConnection(ReadOnlySpan localName, Span remoteName, ref uint remoteNameLength); + + internal static int GetUNCForNetworkDrive(char drive, out string? uncPath) + { + uncPath = null; + if (s_WNetApiNotAvailable) + { + return ERROR_NOT_SUPPORTED; + } + + uint bufferSize = MAX_PATH; + +#if DEBUG + // In Debug mode buffer size is initially set to 3 and if additional buffer is required, the + // required buffer size is allocated and the WNetGetConnection API is executed with the newly + // allocated buffer size. + bufferSize = 3; +#endif + + // TODO: change ushort with char after LibraryImport will support 'ref char' + // without applying the 'System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute' + // to the assembly. + ReadOnlySpan driveName = stackalloc ushort[] { drive, ':', '\0' }; + Span uncBuffer = stackalloc ushort[(int)bufferSize]; + int errorCode; + + try + { + errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize); + } + catch (System.DllNotFoundException) + { + s_WNetApiNotAvailable = true; + return ERROR_NOT_SUPPORTED; + } + + if (errorCode == ERROR_SUCCESS) + { + uncPath = uncBuffer.Slice((int)bufferSize).ToString(); + } + else if (errorCode == ERROR_MORE_DATA) + { + ushort[]? rentedArray = null; + try + { + uncBuffer = rentedArray = ArrayPool.Shared.Rent((int)bufferSize); + errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize); + + if (errorCode == ERROR_SUCCESS) + { + uncPath = uncBuffer.Slice((int)bufferSize).ToString(); + } + } + finally + { + if (rentedArray is not null) + { + ArrayPool.Shared.Return(rentedArray); + } + } + } + + return errorCode; + } + } +} diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 6b18e44b88..4bc9a1d579 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -810,54 +810,23 @@ namespace Microsoft.PowerShell.Commands #endif } +#if !UNIX private static string WinGetUNCForNetworkDrive(string driveName) { - const int ERROR_NO_NETWORK = 1222; string uncPath = null; if (!string.IsNullOrEmpty(driveName) && driveName.Length == 1) { - // By default buffer size is set to 300 which would generally be sufficient in most of the cases. - int bufferSize = 300; -#if DEBUG - // In Debug mode buffer size is initially set to 3 and if additional buffer is required, the - // required buffer size is allocated and the WNetGetConnection API is executed with the newly - // allocated buffer size. - bufferSize = 3; -#endif + int errorCode = Interop.Windows.GetUNCForNetworkDrive(driveName[0], out uncPath); - StringBuilder uncBuffer = new StringBuilder(bufferSize); - driveName += ':'; - - // Call the windows API - int errorCode = ERROR_NO_NETWORK; - - try - { - errorCode = NativeMethods.WNetGetConnection(driveName, uncBuffer, ref bufferSize); - } - catch (System.DllNotFoundException) - { - return null; - } - - // error code 234 is returned whenever the required buffer size is greater - // than the specified buffer size. - if (errorCode == 234) - { - uncBuffer = new StringBuilder(bufferSize); - errorCode = NativeMethods.WNetGetConnection(driveName, uncBuffer, ref bufferSize); - } - - if (errorCode != 0) + if (errorCode != Interop.Windows.ERROR_SUCCESS) { throw new System.ComponentModel.Win32Exception(errorCode); } - - uncPath = uncBuffer.ToString(); } return uncPath; } +#endif /// /// Get the substituted path of a NetWork type MS-DOS device that is created by 'subst' command. @@ -7072,11 +7041,6 @@ namespace Microsoft.PowerShell.Commands #endif } - internal static bool WinPathIsNetworkPath(string path) - { - return NativeMethods.PathIsNetworkPath(path); // call the native method - } - private static partial class NativeMethods { /// @@ -7104,43 +7068,16 @@ namespace Microsoft.PowerShell.Commands /// the connection is returned. [DllImport("mpr.dll", CharSet = CharSet.Unicode)] internal static extern int WNetAddConnection2(ref NetResource netResource, byte[] password, string username, int flags); + } - /// - /// WNetGetConnection function retrieves the name of the network resource associated with a local device. - /// - /// - /// Local name of the PSDrive. - /// - /// - /// The remote name to which the PSDrive is getting mapped to. - /// - /// - /// length of the remote name of the created PSDrive. - /// - /// - [DllImport("mpr.dll", CharSet = CharSet.Unicode)] - internal static extern int WNetGetConnection(string localName, StringBuilder remoteName, ref int remoteNameLength); - -#if CORECLR // TODO:CORECLR Win32 function 'PathIsNetworkPath' is in an extension API set which is currently not on CSS. - /// - /// Searches a path for a drive letter within the range of 'A' to 'Z' and returns the corresponding drive number. - /// - /// - /// Path of the file being executed - /// - /// Returns 0 through 25 (corresponding to 'A' through 'Z') if the path has a drive letter, or -1 otherwise. - [LibraryImport("api-ms-win-core-shlwapi-legacy-l1-1-0.dll", EntryPoint ="PathGetDriveNumberW", StringMarshalling = StringMarshalling.Utf16)] - internal static partial int PathGetDriveNumber(string path); - - private static bool _WNetApiAvailable = true; - - /// - /// The API 'PathIsNetworkPath' is not available in CoreSystem. - /// This implementation is based on the 'PathIsNetworkPath' API. - /// - /// - /// - internal static bool PathIsNetworkPath(string path) +#if !UNIX + /// + /// The API 'PathIsNetworkPath' is not available in CoreSystem. + /// This implementation is based on the 'PathIsNetworkPath' API. + /// + /// A file system path. + /// True if the path is a network path. + internal static bool WinPathIsNetworkPath(string path) { if (string.IsNullOrEmpty(path)) { @@ -7152,33 +7089,17 @@ namespace Microsoft.PowerShell.Commands return true; } - if (!_WNetApiAvailable) + if (path.Length > 1 && path[1] == ':' && char.IsAsciiLetter(path[0])) { - return false; - } - - // 0 - 25 corresponding to 'A' - 'Z' - int driveId = PathGetDriveNumber(path); - if (driveId >= 0 && driveId < 26) - { - string driveName = (char)('A' + driveId) + ":"; - - int bufferSize = 260; // MAX_PATH from EhStorIoctl.h - StringBuilder uncBuffer = new StringBuilder(bufferSize); + // path[0] is ASCII letter, e.g. is in 'A'-'Z' or 'a'-'z'. int errorCode = -1; - try - { - errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize); - } - catch (System.DllNotFoundException) - { - _WNetApiAvailable = false; - return false; - } + errorCode = Interop.Windows.GetUNCForNetworkDrive(path[0], out string _); // From the 'IsNetDrive' API. // 0: success; 1201: connection closed; 31: device error - if (errorCode == 0 || errorCode == 1201 || errorCode == 31) + if (errorCode == Interop.Windows.ERROR_SUCCESS || + errorCode == Interop.Windows.ERROR_CONNECTION_UNAVAIL || + errorCode == Interop.Windows.ERROR_GEN_FAILURE) { return true; } @@ -7186,19 +7107,7 @@ namespace Microsoft.PowerShell.Commands return false; } -#else - /// - /// Facilitates to validate if the supplied path exists locally or on the network share. - /// - /// - /// Path of the file being executed. - /// - /// True if the path is a network path or else returns false. - [LibraryImport("shlwapi.dll", EntryPoint = "PathIsNetworkPathW", StringMarshalling = StringMarshalling.Utf16)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool PathIsNetworkPath(string path); #endif - } /// /// Managed equivalent of NETRESOURCE structure of WNet API.