/* Copyright (C) 2008-2016 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
using Alphaleonis.Win32.Network;
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.AccessControl;
using System.Text;
namespace Alphaleonis.Win32.Filesystem
{
/// Provides static methods to retrieve device resource information from a local or remote host.
public static class Device
{
#region EnumerateDevices
/// Enumerates all available devices on the local host.
/// instances of type from the local host.
/// One of the devices.
[SecurityCritical]
public static IEnumerable EnumerateDevices(DeviceGuid deviceGuid)
{
return EnumerateDevices(null, deviceGuid);
}
/// Enumerates all available devices of type on the local or remote host.
/// instances of type for the specified .
/// The name of the local or remote host on which the device resides. refers to the local host.
/// One of the devices.
[SecurityCritical]
public static IEnumerable EnumerateDevices(string hostName, DeviceGuid deviceGuid)
{
return EnumerateDevicesCore(null, hostName, deviceGuid);
}
#endregion // EnumerateDevices
#region Internal Methods
#region EnumerateDevicesCore
/// Enumerates all available devices on the local or remote host.
[SecurityCritical]
internal static IEnumerable EnumerateDevicesCore(SafeHandle safeHandle, string hostName, DeviceGuid deviceInterfaceGuid)
{
var callerHandle = safeHandle != null;
var deviceGuid = new Guid(Utils.GetEnumDescription(deviceInterfaceGuid));
// CM_Connect_Machine()
// MSDN Note: Beginning in Windows 8 and Windows Server 2012 functionality to access remote machines has been removed.
// You cannot access remote machines when running on these versions of Windows.
// http://msdn.microsoft.com/en-us/library/windows/hardware/ff537948%28v=vs.85%29.aspx
SafeCmConnectMachineHandle safeMachineHandle;
var lastError = NativeMethods.CM_Connect_Machine(Path.LocalToUncCore(Host.GetUncName(hostName), false, false, false), out safeMachineHandle);
if (safeMachineHandle != null && safeMachineHandle.IsInvalid)
{
safeMachineHandle.Close();
NativeError.ThrowException(lastError, Resources.Handle_Is_Invalid);
}
using (safeMachineHandle)
{
// Start at the "Root" of the device tree of the specified machine.
if (!callerHandle)
safeHandle = NativeMethods.SetupDiGetClassDevsEx(ref deviceGuid, IntPtr.Zero, IntPtr.Zero,
NativeMethods.SetupDiGetClassDevsExFlags.Present |
NativeMethods.SetupDiGetClassDevsExFlags.DeviceInterface,
IntPtr.Zero, hostName, IntPtr.Zero);
if (safeHandle != null && safeHandle.IsInvalid)
{
safeHandle.Close();
NativeError.ThrowException(Marshal.GetLastWin32Error(), Resources.Handle_Is_Invalid);
}
try
{
uint memberInterfaceIndex = 0;
var deviceInterfaceData = CreateDeviceInterfaceDataInstance();
// Start enumerating Device Interfaces.
while (NativeMethods.SetupDiEnumDeviceInterfaces(safeHandle, IntPtr.Zero, ref deviceGuid, memberInterfaceIndex++, ref deviceInterfaceData))
{
lastError = Marshal.GetLastWin32Error();
if (lastError != Win32Errors.NO_ERROR)
NativeError.ThrowException(lastError, hostName);
var deviceInfoData = CreateDeviceInfoDataInstance();
var deviceInterfaceDetailData = GetDeviceInterfaceDetailDataInstance(safeHandle, deviceInterfaceData, deviceInfoData);
// Get device interace details.
if (!NativeMethods.SetupDiGetDeviceInterfaceDetail(safeHandle, ref deviceInterfaceData, ref deviceInterfaceDetailData, NativeMethods.DefaultFileBufferSize, IntPtr.Zero, ref deviceInfoData))
{
lastError = Marshal.GetLastWin32Error();
if (lastError != Win32Errors.NO_ERROR)
NativeError.ThrowException(lastError, hostName);
}
// Create DeviceInfo instance.
// Set DevicePath property of DeviceInfo instance.
var deviceInfo = new DeviceInfo(hostName) { DevicePath = deviceInterfaceDetailData.DevicePath };
// Current InstanceId is at the "USBSTOR" level, so we
// need up "move up" one level to get to the "USB" level.
uint ptrPrevious;
// CM_Get_Parent_Ex()
// Note: Using this function to access remote machines is not supported
// beginning with Windows 8 and Windows Server 2012, as this functionality has been removed.
// http://msdn.microsoft.com/en-us/library/windows/hardware/ff538615%28v=vs.85%29.aspx
lastError = NativeMethods.CM_Get_Parent_Ex(out ptrPrevious, deviceInfoData.DevInst, 0, safeMachineHandle);
if (lastError != Win32Errors.CR_SUCCESS)
NativeError.ThrowException(lastError, hostName);
// Now we get the InstanceID of the USB level device.
using (var safeBuffer = new SafeGlobalMemoryBufferHandle(NativeMethods.DefaultFileBufferSize))
{
// CM_Get_Device_ID_Ex()
// Note: Using this function to access remote machines is not supported beginning with Windows 8 and Windows Server 2012,
// as this functionality has been removed.
// http://msdn.microsoft.com/en-us/library/windows/hardware/ff538411%28v=vs.85%29.aspx
lastError = NativeMethods.CM_Get_Device_ID_Ex(deviceInfoData.DevInst, safeBuffer, (uint)safeBuffer.Capacity, 0, safeMachineHandle);
if (lastError != Win32Errors.CR_SUCCESS)
NativeError.ThrowException(lastError, hostName);
// Add to instance.
deviceInfo.InstanceId = safeBuffer.PtrToStringUni();
}
#region Get Registry Properties
using (var safeBuffer = new SafeGlobalMemoryBufferHandle(NativeMethods.DefaultFileBufferSize))
{
uint regType;
string dataString;
var safeBufferCapacity = (uint) safeBuffer.Capacity;
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.BaseContainerId, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
{
dataString = safeBuffer.PtrToStringUni();
if (!Utils.IsNullOrWhiteSpace(dataString))
deviceInfo.BaseContainerId = new Guid(dataString);
}
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.ClassGuid, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
{
dataString = safeBuffer.PtrToStringUni();
if (!Utils.IsNullOrWhiteSpace(dataString))
deviceInfo.ClassGuid = new Guid(dataString);
}
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.Class, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.Class = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.CompatibleIds, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.CompatibleIds = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.DeviceDescription, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.DeviceDescription = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.Driver, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.Driver = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.EnumeratorName, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.EnumeratorName = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.FriendlyName, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.FriendlyName = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.HardwareId, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.HardwareId = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.LocationInformation, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.LocationInformation = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.LocationPaths, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.LocationPaths = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.Manufacturer, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.Manufacturer = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.PhysicalDeviceObjectName, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.PhysicalDeviceObjectName = safeBuffer.PtrToStringUni();
if (NativeMethods.SetupDiGetDeviceRegistryProperty(safeHandle, ref deviceInfoData, NativeMethods.SetupDiGetDeviceRegistryPropertyEnum.Service, out regType, safeBuffer, safeBufferCapacity, IntPtr.Zero))
deviceInfo.Service = safeBuffer.PtrToStringUni();
}
#endregion // Get Registry Properties
yield return deviceInfo;
// Get new structure instance.
deviceInterfaceData = CreateDeviceInterfaceDataInstance();
}
}
finally
{
// Handle is ours, dispose.
if (!callerHandle && safeHandle != null)
safeHandle.Close();
}
}
}
#endregion // EnumerateDevicesCore
#region GetLinkTargetInfoCore
/// Get information about the target of a mount point or symbolic link on an NTFS file system.
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Disposing is controlled.")]
[SecurityCritical]
internal static LinkTargetInfo GetLinkTargetInfoCore(SafeFileHandle safeHandle)
{
// Start with a large buffer to prevent a 2nd call.
// MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384
uint bytesReturned = 4*NativeMethods.DefaultFileBufferSize;
using (var safeBuffer = new SafeGlobalMemoryBufferHandle((int) bytesReturned))
{
while (true)
{
// DeviceIoControlMethod.Buffered = 0,
// DeviceIoControlFileDevice.FileSystem = 9
// FsctlGetReparsePoint = (DeviceIoControlFileDevice.FileSystem << 16) | (42 << 2) | DeviceIoControlMethod.Buffered | (0 << 14)
if (!NativeMethods.DeviceIoControl(safeHandle, ((9 << 16) | (42 << 2) | 0 | (0 << 14)), IntPtr.Zero, 0, safeBuffer, (uint) safeBuffer.Capacity, out bytesReturned, IntPtr.Zero))
{
var lastError = Marshal.GetLastWin32Error();
switch ((uint) lastError)
{
case Win32Errors.ERROR_MORE_DATA:
case Win32Errors.ERROR_INSUFFICIENT_BUFFER:
if (safeBuffer.Capacity < bytesReturned)
{
safeBuffer.Close();
break;
}
NativeError.ThrowException(lastError);
break;
}
}
else
break;
}
var marshalReparseBuffer = (int) Marshal.OffsetOf(typeof(NativeMethods.ReparseDataBufferHeader), "data");
var header = safeBuffer.PtrToStructure(0);
var dataOffset = (int) (marshalReparseBuffer + (header.ReparseTag == ReparsePointTag.MountPoint
? Marshal.OffsetOf(typeof (NativeMethods.MountPointReparseBuffer), "data")
: Marshal.OffsetOf(typeof (NativeMethods.SymbolicLinkReparseBuffer), "data")).ToInt64());
var dataBuffer = new byte[bytesReturned - dataOffset];
switch (header.ReparseTag)
{
case ReparsePointTag.MountPoint:
var mountPoint = safeBuffer.PtrToStructure(marshalReparseBuffer);
safeBuffer.CopyTo(dataOffset, dataBuffer, 0, dataBuffer.Length);
return new LinkTargetInfo(
Encoding.Unicode.GetString(dataBuffer, mountPoint.SubstituteNameOffset, mountPoint.SubstituteNameLength),
Encoding.Unicode.GetString(dataBuffer, mountPoint.PrintNameOffset, mountPoint.PrintNameLength));
case ReparsePointTag.SymLink:
var symLink = safeBuffer.PtrToStructure(marshalReparseBuffer);
safeBuffer.CopyTo(dataOffset, dataBuffer, 0, dataBuffer.Length);
return new SymbolicLinkTargetInfo(
Encoding.Unicode.GetString(dataBuffer, symLink.SubstituteNameOffset, symLink.SubstituteNameLength),
Encoding.Unicode.GetString(dataBuffer, symLink.PrintNameOffset, symLink.PrintNameLength), symLink.Flags);
default:
throw new UnrecognizedReparsePointException();
}
}
}
#endregion // GetLinkTargetInfoCore
#region ToggleCompressionCore
/// Sets the NTFS compression state of a file or directory on a volume whose file system supports per-file and per-directory compression.
/// Specifies that is a file or directory.
/// The transaction.
/// A path that describes a folder or file to compress or decompress.
/// = compress, = decompress
/// Indicates the format of the path parameter(s).
[SecurityCritical]
internal static void ToggleCompressionCore(bool isFolder, KernelTransaction transaction, string path, bool compress, PathFormat pathFormat)
{
using (var handle = File.CreateFileCore(transaction, path, isFolder ? ExtendedFileAttributes.BackupSemantics : ExtendedFileAttributes.Normal, null, FileMode.Open, FileSystemRights.Modify, FileShare.None, true, pathFormat))
{
// DeviceIoControlMethod.Buffered = 0,
// DeviceIoControlFileDevice.FileSystem = 9
// FsctlSetCompression = (DeviceIoControlFileDevice.FileSystem << 16) | (16 << 2) | DeviceIoControlMethod.Buffered | ((FileAccess.Read | FileAccess.Write) << 14)
// 0 = Decompress, 1 = Compress.
InvokeIoControlUnknownSize(handle, ((9 << 16) | (16 << 2) | 0 | ((uint)(FileAccess.Read | FileAccess.Write) << 14)), (compress) ? 1 : 0);
}
}
#endregion // ToggleCompressionCore
#region Private
#region CreateDeviceInfoDataInstance
/// Builds a DeviceInfo Data structure.
/// An initialized NativeMethods.SP_DEVINFO_DATA instance.
[SecurityCritical]
private static NativeMethods.SP_DEVINFO_DATA CreateDeviceInfoDataInstance()
{
var did = new NativeMethods.SP_DEVINFO_DATA();
did.cbSize = (uint) Marshal.SizeOf(did);
return did;
}
#endregion // CreateDeviceInfoDataInstance
#region CreateDeviceInterfaceDataInstance
/// Builds a Device Interface Data structure.
/// An initialized NativeMethods.SP_DEVICE_INTERFACE_DATA instance.
[SecurityCritical]
private static NativeMethods.SP_DEVICE_INTERFACE_DATA CreateDeviceInterfaceDataInstance()
{
var did = new NativeMethods.SP_DEVICE_INTERFACE_DATA();
did.cbSize = (uint) Marshal.SizeOf(did);
return did;
}
#endregion // CreateDeviceInterfaceDataInstance
#region GetDeviceInterfaceDetailDataInstance
/// Builds a Device Interface Detail Data structure.
/// An initialized NativeMethods.SP_DEVICE_INTERFACE_DETAIL_DATA instance.
[SecurityCritical]
private static NativeMethods.SP_DEVICE_INTERFACE_DETAIL_DATA GetDeviceInterfaceDetailDataInstance(SafeHandle safeHandle, NativeMethods.SP_DEVICE_INTERFACE_DATA deviceInterfaceData, NativeMethods.SP_DEVINFO_DATA deviceInfoData)
{
// Build a Device Interface Detail Data structure.
var didd = new NativeMethods.SP_DEVICE_INTERFACE_DETAIL_DATA
{
cbSize = (IntPtr.Size == 4) ? (uint) (Marshal.SystemDefaultCharSize + 4) : 8
};
// Get device interace details.
if (!NativeMethods.SetupDiGetDeviceInterfaceDetail(safeHandle, ref deviceInterfaceData, ref didd, NativeMethods.DefaultFileBufferSize, IntPtr.Zero, ref deviceInfoData))
{
var lastError = Marshal.GetLastWin32Error();
if (lastError != Win32Errors.NO_ERROR)
NativeError.ThrowException(lastError);
}
return didd;
}
#endregion // GetDeviceInterfaceDetailDataInstance
#region InvokeIoControlUnknownSize
/// Repeatedly invokes InvokeIoControl with the specified input until enough memory has been allocated.
[SecurityCritical]
private static byte[] InvokeIoControlUnknownSize(SafeFileHandle handle, uint controlCode, TV input, uint increment = 128)
{
byte[] output;
uint bytesReturned;
var inputSize = (uint) Marshal.SizeOf(input);
var outputLength = increment;
do
{
output = new byte[outputLength];
if (!NativeMethods.DeviceIoControl(handle, controlCode, input, inputSize, output, outputLength, out bytesReturned, IntPtr.Zero))
{
var lastError = Marshal.GetLastWin32Error();
switch ((uint)lastError)
{
case Win32Errors.ERROR_MORE_DATA:
case Win32Errors.ERROR_INSUFFICIENT_BUFFER:
outputLength += increment;
break;
default:
NativeError.ThrowException(lastError);
break;
}
}
else
break;
} while (true);
// Return the result
if (output.Length == bytesReturned)
return output;
var res = new byte[bytesReturned];
Array.Copy(output, res, bytesReturned);
return res;
}
#endregion // InvokeIoControlUnknownSize
#endregion // Private
#endregion // Internal Methods
}
}