C# 仅列出';设备和打印机';面板

C# 仅列出';设备和打印机';面板,c#,windows,enumerate-devices,C#,Windows,Enumerate Devices,我正在用C语言编写一个应用程序,它将在用户PC上运行,我只想列出Windows“设备和打印机”控制面板中显示的设备,如显示器、键盘、鼠标、扬声器等 我可以使用WMI提取所有设备的列表,但是否有办法仅提取控制面板该部分中显示的设备,而不是完整列表 我在网上搜索了一下,没有发现任何与此相关的信息,我甚至找不到设备出现在该列表中的条件 是否可以访问该列表中显示的那些设备的列表,或者,如果没有,是否有一个过滤器可以应用于只显示这些设备的完整列表 提前感谢我使用p/invoke和COM互操作来完成这项工作

我正在用C语言编写一个应用程序,它将在用户PC上运行,我只想列出Windows“设备和打印机”控制面板中显示的设备,如显示器、键盘、鼠标、扬声器等

我可以使用WMI提取所有设备的列表,但是否有办法仅提取控制面板该部分中显示的设备,而不是完整列表

我在网上搜索了一下,没有发现任何与此相关的信息,我甚至找不到设备出现在该列表中的条件

是否可以访问该列表中显示的那些设备的列表,或者,如果没有,是否有一个过滤器可以应用于只显示这些设备的完整列表


提前感谢

我使用p/invoke和COM互操作来完成这项工作,方法是枚举中的shell项,并通过确保包含以
PrintFax
开头的项进行筛选

无法将必要的互操作定义浓缩到一个答案中,但这是我用于枚举显示名称、DEVMODE名称和任意大小的图像的逻辑:

public sealed class PrinterInfo
{
    public string IdName { get; }
    public string DisplayName { get; }
    public Bitmap Image { get; }

    private PrinterInfo(string idName, string displayName, Bitmap image)
    {
        IdName = idName;
        DisplayName = displayName;
        Image = image;
    }

    public static IReadOnlyList<PrinterInfo> GetInstalledPrinterNamesAndImages(Size imageSize)
    {
        var r = new List<PrinterInfo>();

        using (var folderIdList = CreateDevicesAndPrintersIDL())
        {
            var folder = GetShellFolder(folderIdList);
            var enumerator = folder.EnumObjects(IntPtr.Zero, SHCONTF.NONFOLDERS);

            for (;;)
            {
                // If you request more than are left, actualCount is 0, so we'll do one at a time.
                var next = enumerator.Next(1, out var relativeIdList, out var actualCount);
                next.ThrowIfError();
                if (next == HResult.False || actualCount != 1) break; // printerChild is junk

                using (relativeIdList)
                using (var absoluteIdList = ILCombine(folderIdList, relativeIdList))
                {
                    var shellItem = GetShellItem(absoluteIdList);
                    var idName = GetPrinterFriendlyNameIfPrinter(shellItem);
                    if (idName != null)
                        r.Add(new PrinterInfo(idName, GetDisplayName(shellItem), GetImage(shellItem, imageSize)));
                }
            }
        }

        return r;
    }

    private static ItemIdListSafeHandle CreateDevicesAndPrintersIDL()
    {
        SHGetKnownFolderIDList(FOLDERID.ControlPanelFolder, KF_FLAG.DEFAULT, IntPtr.Zero, out var controlPanelIdList).ThrowIfError();
        using (controlPanelIdList)
        {
            GetShellFolder(controlPanelIdList).ParseDisplayName(IntPtr.Zero, null, "::{A8A91A66-3A7D-4424-8D24-04E180695C7A}", IntPtr.Zero, out var childDevicesAndPriversIdList, IntPtr.Zero);
            using (childDevicesAndPriversIdList)
                return ILCombine(controlPanelIdList, childDevicesAndPriversIdList);
        }
    }

    private static string GetPrinterFriendlyNameIfPrinter(IShellItem2 shellItem)
    {
        // Devices_PrimaryCategory returns "Printers" for printers and faxes on Windows 10 but "Printers and faxes" on Windows 7.
        using (var categoryIds = new PropVariantSafeHandle())
        {
            shellItem.GetProperty(PKEY.Devices_CategoryIds, categoryIds).ThrowIfError();
            if (!categoryIds.ToStringVector().Any(id => id.StartsWith("PrintFax", StringComparison.OrdinalIgnoreCase)))
                return null;
        }

        // The canonical or "friendly name" needed to match the devmode
        // https://blogs.msdn.microsoft.com/asklar/2009/10/21/getting-the-printer-friendly-name-from-the-device-center-shell-folder/
        // PKEY_Devices_InterfacePaths doesn't seem to ever be found, but PKEY_Devices_FriendlyName works so...
        shellItem.GetString(PKEY.Devices_FriendlyName, out var friendlyName).ThrowIfError();
        return friendlyName.ReadAndFree();
    }

    private static string GetDisplayName(IShellItem2 shellItem)
    {
        return shellItem.GetDisplayName(SIGDN.NORMALDISPLAY).ReadAndFree();
    }

    private static Bitmap GetImage(IShellItem2 shellItem, Size imageSize)
    {
        return ((IShellItemImageFactory)shellItem).GetImage(new POINT(imageSize.Width, imageSize.Height), SIIGBF.SIIGBF_BIGGERSIZEOK)
            .CopyAndFree(); // Bitmap.FromHbitmap is useless with alpha, so make a copy
    }


    private static IShellFolder GetShellFolder(ItemIdListSafeHandle itemIdList)
    {
        SHBindToObject(IntPtr.Zero, itemIdList, null, typeof(IShellFolder).GUID, out var objShellFolder).ThrowIfError();
        return (IShellFolder)objShellFolder;
    }

    private static IShellItem2 GetShellItem(ItemIdListSafeHandle itemIdList)
    {
        SHCreateItemFromIDList(itemIdList, typeof(IShellItem2).GUID, out var objShellItem).ThrowIfError();
        return (IShellItem2)objShellItem;
    }
}
公共密封类PrinterInfo
{
公共字符串IdName{get;}
公共字符串DisplayName{get;}
公共位图图像{get;}
私有PrinterInfo(字符串idName、字符串displayName、位图图像)
{
IdName=IdName;
DisplayName=DisplayName;
图像=图像;
}
公共静态IReadOnlyList GetInstalledPrinterNamesAndImages(大小imageSize)
{
var r=新列表();
使用(var folderIdList=CreateDevicesAndPrintersIDL())
{
var folder=GetShellFolder(FolderId列表);
var enumerator=folder.EnumObjects(IntPtr.Zero,SHCONTF.NONFOLDERS);
对于(;;)
{
//如果您请求的数量超过剩余数量,则实际数量为0,因此我们将一次执行一个。
var next=枚举数.next(1,out var relativeIdList,out var ACTIVALCOUNT);
next.throwiferor();
如果(next==HResult.False | | actualCount!=1)break;//printerChild是垃圾
使用(列表)
使用(var AbsolutedList=ILCombine(FolderId列表,RelativeId列表))
{
var shellItem=GetShellItem(绝对IDList);
变量idName=GetPrinterFriendlyNameIfPrinter(shellItem);
if(idName!=null)
r、 添加(新的PrinterInfo(idName、GetDisplayName(shellItem)、GetImage(shellItem、imageSize));
}
}
}
返回r;
}
私有静态项IDListSafeHandle CreateDevicesAndPrintersIDL()
{
SHGetKnownFolderIDList(FOLDERID.ControlPanelFolder,KF_FLAG.DEFAULT,IntPtr.Zero,out var controlPanelIdList);
使用(controlPanelIdList)
{
GetShellFolder(controlPanelIdList).ParseDisplayName(IntPtr.Zero,null,:{A8A91A66-3A7D-4424-8D24-04E180695C7A}),IntPtr.Zero,out-var-childDevicesAndPriversIdList,IntPtr.Zero;
使用(儿童设备和隐私列表)
返回ILCombine(控制面板IDList、儿童设备和私人列表);
}
}
私有静态字符串GetPrinterFriendlyNameIfPrinter(IShellItem2 shellItem)
{
//Devices_PrimaryCategory在Windows 10上返回打印机和传真的“打印机”,但在Windows 7上返回“打印机和传真”。
使用(var categoryId=new-PropVariantSafeHandle())
{
GetProperty(PKEY.Devices_categoryId,categoryId).throwiferor();
如果(!categoryIds.ToStringVector().Any(id=>id.StartsWith(“PrintFax”,StringComparison.OrdinalIgnoreCase)))
返回null;
}
//匹配devmode所需的规范名称或“友好名称”
// https://blogs.msdn.microsoft.com/asklar/2009/10/21/getting-the-printer-friendly-name-from-the-device-center-shell-folder/
//PKEY_Devices_InterfacePath似乎从未被找到,但PKEY_Devices_FriendlyName的工作原理是。。。
GetString(PKEY.Devices_FriendlyName,out var FriendlyName).throwiferor();
返回friendlyName.ReadAndFree();
}
私有静态字符串GetDisplayName(IShellItem2 shellItem)
{
返回shellItem.GetDisplayName(SIGDN.NORMALDISPLAY.ReadAndFree();
}
私有静态位图GetImage(IShellItem2 shellItem,大小imageSize)
{
返回((IShellItemImageFactory)shellItem).GetImage(新点(imageSize.Width,imageSize.Height),SIIGBF.SIIGBF_BIGGERSIZEOK)
.CopyAndFree();//Bitmap.FromHbitmap对alpha无效,因此请复制一份
}
私有静态IShellFolder GetShellFolder(ItemIdListSafeHandle itemIdList)
{
SHBindToObject(IntPtr.Zero,itemIdList,null,typeof(IShellFolder).GUID,out var objShellFolder).throwiferor();
返回(IShellFolder)objShellFolder;
}
私有静态IShellItem2 GetShellItem(ItemIdListSafeHandle itemIdList)
{
SHCreateItemFromIDList(itemIdList,typeof(IShellItem2).GUID,out var objShellItem).throwiferor();
返回(IShellItem2)objShellItem;
}
}
(C#7)

以下是您可以编译的完整演示:

对于不需要ValueTuple的C#6版本,请参阅


我很乐意回答任何问题。

我在C#中使用p/invoke和COM interop,通过枚举中的shell项,并通过确保包含以
PrintFax
开头的项来进行筛选。如果您感兴趣,很高兴与大家分享该代码,但其中没有一个是WMI。查看该代码会很好-我不会将WMI作为解决方案:)谢谢您的帮助,我会查看并测试它,如果有任何问题,请告诉您:)