mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Replace DllImport with LibraryImport in SMA 2 (#18543)
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#nullable enable
|
||||
|
||||
#if !UNIX
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.ComponentModel;
|
||||
using System.Management.Automation;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
internal static partial class Interop
|
||||
{
|
||||
internal static partial class Windows
|
||||
{
|
||||
[LibraryImport(PinvokeDllNames.QueryDosDeviceDllName, EntryPoint = "QueryDosDeviceW", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)]
|
||||
internal static partial int QueryDosDevice(Span<char> lpDeviceName, Span<char> lpTargetPath, uint ucchMax);
|
||||
|
||||
internal static string GetDosDeviceForNetworkPath(char deviceName)
|
||||
{
|
||||
// By default buffer size is set to 300 which would generally be sufficient in most of the cases.
|
||||
const int StartLength =
|
||||
#if DEBUG
|
||||
// In debug, validate ArrayPool growth.
|
||||
1;
|
||||
#else
|
||||
300;
|
||||
#endif
|
||||
|
||||
Span<char> buffer = stackalloc char[StartLength + 1];
|
||||
Span<char> fullDeviceName = stackalloc char[3] { deviceName, ':', '\0' };
|
||||
char[]? rentedArray = null;
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
uint length = (uint)buffer.Length;
|
||||
int retValue = QueryDosDevice(fullDeviceName, buffer, length);
|
||||
if (retValue > 0)
|
||||
{
|
||||
if (buffer.StartsWith("\\??\\"))
|
||||
{
|
||||
// QueryDosDevice always return array of NULL-terinating strings with additional final NULL
|
||||
// so the buffer has always two NULL-s on end.
|
||||
//
|
||||
// "\\??\\UNC\\localhost\\c$\\tmp\0\0" -> "UNC\\localhost\\c$\\tmp\0\0"
|
||||
Span<char> res = buffer.Slice(4);
|
||||
if (res.StartsWith("UNC"))
|
||||
{
|
||||
// -> "C\\localhost\\c$\\tmp\0\0" -> "\\\\localhost\\c$\\tmp"
|
||||
//
|
||||
// We need to take only first null-terminated string as QueryDosDevice() docs say.
|
||||
int i = 3;
|
||||
for (; i < res.Length; i++)
|
||||
{
|
||||
if (res[i] == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Diagnostics.Assert(i < res.Length, "Broken QueryDosDevice() buffer.");
|
||||
|
||||
res = res.Slice(2, i);
|
||||
res[0] = '\\';
|
||||
|
||||
// If we want always to have terminating slash -> "\\\\localhost\\c$\\tmp\\"
|
||||
// res = res.Slice(2, retValue - 3);
|
||||
// res[0] = '\\';
|
||||
// res[^1] = '\\';
|
||||
}
|
||||
// else if (res[^3] == ':')
|
||||
// {
|
||||
// Diagnostics.Assert(false, "Really it is a dead code since GetDosDevice() is called only if PSDrive.DriveType == DriveType.Network");
|
||||
|
||||
// // The substed path is the root path of a drive. For example: subst Y: C:\
|
||||
// // -> "C:\0\0" -> "C:\"
|
||||
// res = res.Slice(0, retValue - 1);
|
||||
// res[^1] = '\\';
|
||||
// }
|
||||
else
|
||||
{
|
||||
throw new Exception("GetDosDeviceForNetworkPath() can be called only if PSDrive.DriveType == DriveType.Network.");
|
||||
}
|
||||
|
||||
return res.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
Diagnostics.Assert(false, "Really it is a dead code since GetDosDevice() is called only if PSDrive.DriveType == DriveType.Network");
|
||||
|
||||
// The drive name is not a substed path, then we return the root path of the drive
|
||||
// "C:\0" -> "C:\\"
|
||||
fullDeviceName[^1] = '\\';
|
||||
return fullDeviceName.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
const int ERROR_INSUFFICIENT_BUFFER = 122;
|
||||
int errorCode = Marshal.GetLastPInvokeError();
|
||||
if (errorCode != ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
throw new Win32Exception((int)errorCode);
|
||||
}
|
||||
|
||||
char[]? toReturn = rentedArray;
|
||||
buffer = rentedArray = ArrayPool<char>.Shared.Rent(buffer.Length * 2);
|
||||
if (toReturn is not null)
|
||||
{
|
||||
ArrayPool<char>.Shared.Return(toReturn);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (rentedArray is not null)
|
||||
{
|
||||
ArrayPool<char>.Shared.Return(rentedArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -879,9 +879,9 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
#if UNIX
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
#else
|
||||
return WinGetSubstitutedPathForNetworkDosDevice(driveName);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string WinGetSubstitutedPathForNetworkDosDevice(string driveName)
|
||||
@@ -889,76 +889,12 @@ namespace Microsoft.PowerShell.Commands
|
||||
string associatedPath = 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;
|
||||
var pathInfo = new StringBuilder(bufferSize);
|
||||
driveName += ':';
|
||||
|
||||
// Call the windows API
|
||||
while (true)
|
||||
{
|
||||
pathInfo.EnsureCapacity(bufferSize);
|
||||
int retValue = NativeMethods.QueryDosDevice(driveName, pathInfo, bufferSize);
|
||||
if (retValue > 0)
|
||||
{
|
||||
// If the drive letter is a substed path, the result will be in the format of
|
||||
// - "\??\C:\RealPath" for local path
|
||||
// - "\??\UNC\RealPath" for network path
|
||||
associatedPath = pathInfo.ToString();
|
||||
if (associatedPath.StartsWith("\\??\\", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
associatedPath = associatedPath.Remove(0, 4);
|
||||
if (associatedPath.StartsWith("UNC", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
associatedPath = associatedPath.Remove(0, 3);
|
||||
associatedPath = "\\" + associatedPath;
|
||||
}
|
||||
else if (associatedPath.EndsWith(':'))
|
||||
{
|
||||
// The substed path is the root path of a drive. For example: subst Y: C:\
|
||||
associatedPath += Path.DirectorySeparatorChar;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The drive name is not a substed path, then we return the root path of the drive
|
||||
associatedPath = driveName + "\\";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Windows API call failed
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
if (errorCode != 122)
|
||||
{
|
||||
// ERROR_INSUFFICIENT_BUFFER = 122
|
||||
// For an error other than "insufficient buffer", throw it
|
||||
throw new Win32Exception((int)errorCode);
|
||||
}
|
||||
|
||||
// We got the "insufficient buffer" error. In this case we extend
|
||||
// the buffer size, unless it's unreasonably too large.
|
||||
if (bufferSize >= 32767)
|
||||
{
|
||||
// "The Windows API has many functions that also have Unicode versions to permit
|
||||
// an extended-length path for a maximum total path length of 32,767 characters"
|
||||
// See https://msdn.microsoft.com/library/aa365247.aspx#maxpath
|
||||
string errorMsg = StringUtil.Format(FileSystemProviderStrings.SubstitutePathTooLong, driveName);
|
||||
throw new InvalidOperationException(errorMsg);
|
||||
}
|
||||
|
||||
// Extend the buffer size and try again.
|
||||
bufferSize *= 10;
|
||||
if (bufferSize > 32767)
|
||||
{
|
||||
bufferSize = 32767;
|
||||
}
|
||||
}
|
||||
associatedPath = Interop.Windows.GetDosDeviceForNetworkPath(driveName[0]);
|
||||
}
|
||||
|
||||
return associatedPath;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the root path for a network drive or MS-DOS device.
|
||||
@@ -7293,26 +7229,6 @@ namespace Microsoft.PowerShell.Commands
|
||||
internal static partial bool PathIsNetworkPath(string path);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The function can obtain the current mapping for a particular MS-DOS device name.
|
||||
///
|
||||
/// If lpDeviceName is non-NULL, the function retrieves information about the particular MS-DOS device specified by lpDeviceName.
|
||||
/// The first null-terminated string stored into the buffer is the current mapping for the device.
|
||||
/// The other null-terminated strings represent undeleted prior mappings for the device.
|
||||
/// </summary>
|
||||
/// <param name="lpDeviceName">
|
||||
/// The particular MS-DOS device name.
|
||||
/// </param>
|
||||
/// <param name="lpTargetPath">
|
||||
/// The buffer to receive the result of the query.
|
||||
/// </param>
|
||||
/// <param name="ucchMax">
|
||||
/// The maximum number of characters that can be stored into the buffer
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
[DllImport(PinvokeDllNames.QueryDosDeviceDllName, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern int QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a symbolic link using the native API.
|
||||
/// </summary>
|
||||
|
||||
@@ -273,9 +273,6 @@
|
||||
<data name="NoFirstLastWaitForRaw" xml:space="preserve">
|
||||
<value>The '{0}' and '{1}' parameters cannot be specified in the same command.</value>
|
||||
</data>
|
||||
<data name="SubstitutePathTooLong" xml:space="preserve">
|
||||
<value>The substitute path for the DOS device '{0}' is too long. It exceeds the maximum total path length (32,767 characters) that is valid for the Windows API.</value>
|
||||
</data>
|
||||
<data name="ItemNotDirectory" xml:space="preserve">
|
||||
<value>A directory is required for the operation. The item '{0}' is not a directory.</value>
|
||||
</data>
|
||||
|
||||
+1
-1
@@ -659,7 +659,7 @@ Describe "FileSystem Provider Extended Tests for Get-ChildItem cmdlet" -Tags "CI
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Validate Get-Item ResolvedTarget property" -Tag RequireAdminOnWindows {
|
||||
Describe "Validate Get-Item ResolvedTarget property" -Tags "Feature","RequireAdminOnWindows" {
|
||||
BeforeAll {
|
||||
$rootDir = Join-Path "TestDrive:" "TestDir"
|
||||
|
||||
|
||||
@@ -65,3 +65,17 @@ Describe "Temp: drive" -Tag Feature {
|
||||
$res.Root | Should -BeExactly ([System.IO.Path]::GetTempPath())
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Get-PSDrive for network path" -Tags "Feature","RequireAdminOnWindows" {
|
||||
|
||||
It 'Check P/Invoke GetDosDevice/QueryDosDevice' -Skip:(-not $IsWindows) {
|
||||
$UsedDrives = Get-PSDrive | Select-Object -ExpandProperty Name
|
||||
$PSDriveName = 'D'..'Z' | Where-Object -FilterScript {$_ -notin $UsedDrives} | Get-Random
|
||||
|
||||
subst "$($PSDriveName):" \\localhost\c$\Windows
|
||||
|
||||
$drive = Get-PSDrive $PSDriveName
|
||||
$drive.DisplayRoot | Should -BeExactly '\\localhost\c$\Windows'
|
||||
subst "$($PSDriveName):" /D
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user