Programmatically select multiple files in windows explorer - windows

I can display and select a single file in windows explorer like this:
explorer.exe /select, "c:\path\to\file.txt"
However, I can't work out how to select more than one file. None of the permutations of select I've tried work.
Note: I looked at these pages for docs, neither helped.
https://support.microsoft.com/kb/314853
http://web.archive.org/web/20100716112458/http://www.infocellar.com:80/Win98/explorer-switches.htm

This should be possible with the shell function SHOpenFolderAndSelectItems
EDIT
Here is some sample code showing how to use the function in C/C++, without error checking:
//Directory to open
ITEMIDLIST *dir = ILCreateFromPath(_T("C:\\"));
//Items in directory to select
ITEMIDLIST *item1 = ILCreateFromPath(_T("C:\\Program Files\\"));
ITEMIDLIST *item2 = ILCreateFromPath(_T("C:\\Windows\\"));
const ITEMIDLIST* selection[] = {item1,item2};
UINT count = sizeof(selection) / sizeof(ITEMIDLIST);
//Perform selection
SHOpenFolderAndSelectItems(dir, count, selection, 0);
//Free resources
ILFree(dir);
ILFree(item1);
ILFree(item2);

The true way of selecting multiple files in Explorer is the next
Unmanaged code looks like this (compiled from China code posts with fixing its bugs)
static class NativeMethods
{
[DllImport("shell32.dll", ExactSpelling = true)]
public static extern int SHOpenFolderAndSelectItems(
IntPtr pidlFolder,
uint cidl,
[In, MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl,
uint dwFlags);
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr ILCreateFromPath([MarshalAs(UnmanagedType.LPTStr)] string pszPath);
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214F9-0000-0000-C000-000000000046")]
public interface IShellLinkW
{
[PreserveSig]
int GetPath(StringBuilder pszFile, int cch, [In, Out] ref WIN32_FIND_DATAW pfd, uint fFlags);
[PreserveSig]
int GetIDList([Out] out IntPtr ppidl);
[PreserveSig]
int SetIDList([In] ref IntPtr pidl);
[PreserveSig]
int GetDescription(StringBuilder pszName, int cch);
[PreserveSig]
int SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
[PreserveSig]
int GetWorkingDirectory(StringBuilder pszDir, int cch);
[PreserveSig]
int SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
[PreserveSig]
int GetArguments(StringBuilder pszArgs, int cch);
[PreserveSig]
int SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
[PreserveSig]
int GetHotkey([Out] out ushort pwHotkey);
[PreserveSig]
int SetHotkey(ushort wHotkey);
[PreserveSig]
int GetShowCmd([Out] out int piShowCmd);
[PreserveSig]
int SetShowCmd(int iShowCmd);
[PreserveSig]
int GetIconLocation(StringBuilder pszIconPath, int cch, [Out] out int piIcon);
[PreserveSig]
int SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
[PreserveSig]
int SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved);
[PreserveSig]
int Resolve(IntPtr hwnd, uint fFlags);
[PreserveSig]
int SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
}
[Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode), BestFitMapping(false)]
public struct WIN32_FIND_DATAW
{
public uint dwFileAttributes;
public FILETIME ftCreationTime;
public FILETIME ftLastAccessTime;
public FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
public static void OpenFolderAndSelectFiles(string folder, params string[] filesToSelect)
{
IntPtr dir = ILCreateFromPath(folder);
var filesToSelectIntPtrs = new IntPtr[filesToSelect.Length];
for (int i = 0; i < filesToSelect.Length; i++)
{
filesToSelectIntPtrs[i] = ILCreateFromPath(filesToSelect[i]);
}
SHOpenFolderAndSelectItems(dir, (uint) filesToSelect.Length, filesToSelectIntPtrs, 0);
ReleaseComObject(dir);
ReleaseComObject(filesToSelectIntPtrs);
}
private static void ReleaseComObject(params object[] comObjs)
{
foreach (object obj in comObjs)
{
if (obj != null && Marshal.IsComObject(obj))
Marshal.ReleaseComObject(obj);
}
}
}

it cannot be done through explorer.exe

Depending on what you actually want to accomplish you may be able to do it with AutoHotKey. It is an amazing free tool for automating things you normally can't do. It should come with Windows. This script will select your file and highlight the next two files below it when you hit F12.
F12::
run explorer.exe /select`, "c:\path\to\file.txt"
SendInput {Shift Down}{Down}{Down}{Shift Up}
return
It is also possible to just put those two middle lines in a text file and then pass it is a parm to autohotkey.exe. They have an option to compile the script also, which would make it a standalone exe that you could call. Works great with a great help file.
#Orion, It is possible to use autohotkey from C#. You can make an autohotkey script into a standalone executable (about 400k) that can be launched by your C# app (just the way you are launching explorer). You can also pass it command line parameters. It does not have any runtime requirements.

There are COM Automation LateBinding IDispatch interfaces, these are easy to use from PowerShell, Visual Basic.NET and C#, some sample code:
$shell = New-Object -ComObject Shell.Application
function SelectFiles($filesToSelect)
{
foreach ($fileToSelect in $filesToSelect)
{
foreach ($window in $shell.Windows())
{
foreach ($folderItem in $window.Document.Folder.Items())
{
if ($folderItem.Path -eq $fileToSelect)
{
$window.Document.SelectItem($folderItem, 1 + 8)
}
}
}
}
}
-
Option Strict Off
Imports Microsoft.VisualBasic
Public Class ExplorerHelp
Shared ShellApp As Object = CreateObject("Shell.Application")
Shared Sub SelectFile(filepath As String)
For Each i In ShellApp.Windows
For Each i2 In i.Document.Folder.Items()
If i2.Path = filepath Then
i.Document.SelectItem(i2, 1 + 8)
Exit Sub
End If
Next
Next
End Sub
End Class
https://learn.microsoft.com/en-us/windows/win32/shell/shellfolderview-selectitem

This is one of those questions where it may be good to consider what you're trying to achieve, and whether there's a better method.
To add some more context -
Our company develops a C# client application, which allows users to load files and do stuff with them, kind of like how iTunes manages your MP3 files without showing you the actual file on disk.
It's useful to select a file in the application, and do a 'Show me this file in Windows Explorer` command - this is what I'm trying to achieve, and have done so for single files.
We have a ListView which allows users to select multiple files within the application, and move/delete/etc them. It would be nice to have this 'show me this file in windows' command work for multiple selected files - at least if all the source files are in the same directory, but if it's not possible then it's not a major feature.

I suppose you can use FindWindowEx to get the SysListView32 of Windows Explorer, then use SendMessage with LVM_SETITEMSTATE to select the items. The difficulty being to know the position of the items... Perhaps LVM_FINDITEM can be used for this.

Grr i would like to do this as well. Media Player does it when you select 2+ files and right click and do "open file location" but not exactly sure how (nor do i really feel like spending the time w/ procmon to figure it out).

Related

Highlight Pointer Position

I have a strange Requierment for a Task. In gernerall terms, i need to prepare Windows PCs for Presentations with a PowerShell Script and one of the Requierments is to activate the "hightlight pointer position if ctrl is hit" function in the extended pointer settings.
My first idea was to change the reg-key but if that even works, i think it would be linked to a PC-Restart. I allready tryed to change the Value of the key after finding the diffrenece in the on and off state of the function but i cant force it to "refresh the setting" or it simply does not work.
Any ideas, workaround or the like would be appriciated.
The function is controlled by the Reg-Key HKCU:\Control Panel\Desktop - UserPrefernecesMask
The 'Mouse Sonar' function can be enabled or disabled using the SystemParametersInfo function inside user32.dll.
To use that in PowerShell, add a bit of CSharp:
Add-Type -TypeDefinition #'
using System.Runtime.InteropServices;
public class MouseSonar {
public const uint SPI_SETMOUSESONAR = 0x101D;
public const uint SPIF_UPDATEINIFILE = 0x01;
public const uint SPIF_SENDCHANGE = 0x02;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo (uint uAction, uint uParam, bool lpvParam, uint fuWinIni);
public static void SetSonar (bool Enable) {
SystemParametersInfo(SPI_SETMOUSESONAR, 0, Enable, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}
}
'#
and use it like this:
# turn Mouse Sonar on
[MouseSonar]::SetSonar($true)
# to switch Mouse Sonar off again:
[MouseSonar]::SetSonar($false)
No need to restart the computer, this works immediately.

NtQueryInformationFile for FileStreamInformation succeeds in 32-bit fails in 64-bit

Edit: this question was originally about behavioural difference between code in C++ and C# but that proved to be a red herring. Question has been rewritten to better reflect the problem I'm having.
I'm trying to list the alternate streams of a file hosted on an SMB server using the NtQueryInformationFile function. The code I've written so far works correctly with most servers but fails sometimes when run against non-samba Mac OS X SMB server.
I've reduced the code form my application to the following bit of code that reproduces the issue reliably:
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Streams
{
class Program
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class NETRESOURCE
{
public uint dwScope;
public uint dwType;
public uint dwDisplayType;
public uint dwUsage;
public string lpLocalName;
public string lpRemoteName;
public string lpComment;
public string lpProvider;
}
[DllImport("ntdll.dll", SetLastError = false, CharSet = CharSet.Unicode)]
public static extern uint NtQueryInformationFile(
SafeFileHandle handle,
IntPtr IoStatusBlock,
IntPtr pInfoBlock,
uint length,
uint fileInformationClass);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern SafeFileHandle CreateFile(
string fileName,
uint desiredAccessMask,
uint shareMode,
IntPtr lpSecurityAttributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("mpr.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint WNetAddConnection2(NETRESOURCE netResource, [MarshalAs(UnmanagedType.LPTStr)] string password, [MarshalAs(UnmanagedType.LPTStr)] string username, uint flags);
static void Main(string[] args)
{
var server = args[0];
var username = args[1];
var password = args[2];
var uncPath = args[3];
NETRESOURCE netResource = new NETRESOURCE();
netResource.dwType = 0x1 /* DISK */;
netResource.lpRemoteName = server;
WNetAddConnection2(netResource, password, username, 0x00000004 /* CONNECT_TEMPORARY */);
var fh = CreateFile(
uncPath,
0x80000000 /* GENERIC_READ */,
(uint)(FileShare.Read | FileShare.Write | FileShare.Delete),
IntPtr.Zero,
3 /* OPEN_EXISTING */,
0x08000000 /* SequentialScan */ | 0x02000000 /* BackupSemantics */,
IntPtr.Zero
);
IntPtr status = Marshal.AllocHGlobal(1024);
uint bufferSize = 64*1024;
IntPtr buffer = Marshal.AllocHGlobal((int)bufferSize);
uint ntStatus = NtQueryInformationFile(fh, status, buffer, bufferSize, 22 /* FileStreamInformation */);
Console.WriteLine($"NtQueryFileInformation returned {ntStatus}");
Console.ReadKey();
}
}
}
In most cases the value of ntStatus is STATUS_OK as is expected. When I try to use this on a file that has alternate data streams with a Mac OS X SMB server I get STATUS_INVALID_NETWORK_RESPONSE instead. That error code seems to indicate that the response from the server is incorrect, but when I inspect this with Wireshark there are no differences compared to when STATUS_OK is returned.
To make things even stranger, this code works when I run it in 32-bit mode (set the 'Prefer 32-bit' in the Visual Studio project settings). When run in 64-bit mode I always get the error code.
The obvious error then would be that the code in question is not 64-bit clean, but AFAICT the code above is.
I've also tried replacing the NtQueryInformationFile call with the call to GetFileInformationByHandleEx, but that gives me the same result (with a Win32 error code instead of an NT status code of course).
What could explain this difference in behaviour between 32-bit and 64-bit mode?

Figuring which printer name corresponds to which device ID

My goal is to open a printer connected via USB using the CreateFile (and then issue some WriteFiles and ReadFiles).
If the printer was an LPT one, I would simply do CreateFile("LPT1:", ...). But for USB printers, there is a special device path that must be passed to CreateFile in order to open that printer.
This device path, as I was able to find, is retrieved via SetupDiGetClassDevs -> SetupDiEnumDeviceInterfaces -> SetupDiGetDeviceInterfaceDetail -> DevicePath member and looks like this:
\\?\usb#vid_0a5f&pid_0027#46a072900549#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}
All that is fine, but what I have as the input is the human-readable printer name, as seen in Devices and Printers. The SetupDi* functions don't seem to use that, they only operate on device instance IDs. So the question is now how to get device instance ID from the printer name one would pass to OpenPrinter.
It's not difficult to observe that the GUID part of the above is the GUID_DEVINTERFACE_USBPRINT, and \\?\usb is fixed, so the only bit I'm really interested in is vid_0a5f&pid_0027#46a072900549#. This path I can easily look up manually in the printer properties dialog:
Go to Devices and Printers
Right-click the printer
Properties
Switch to Hardware tab
Select the printing device, such as ZDesigner LP2844-Z
Properties
Switch to Details tab
Select 'Parent' from the dropdown.
But I have no idea how to do that programmatically provided the only thing given is the printer name as seen in the Device and Printers panel.
P.S. 1: I'm not interested in opening the printer with OpenPrinter and then using WritePrinter / ReadPrinter. That has been done, works fine, but now the goal is different.
P.S. 2: I'll be OK with a simpler way to convert the readable printer name to something that can be passed to CreateFile.
P.S. 3: This question, to which I have posted an answer, is very related to what I ultimately want to do.
P.S. 4: The other way round is also fine: If it is possible to obtain the readable name from the SP_DEVINFO_DATA structure, that will also be the answer, although a less convenient one.
Below is what I finally have been able to come up with.
Please confirm that SYSTEM\CurrentControlSet\Control\Print\Printers\{0}\PNPData is a supported path, and not just happens to be there in the current implementation, subject to future changes.
There's a little problem with structure alignment, for which I've posted a separate question.
public static class UsbPrinterResolver
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SP_DEVINFO_DATA
{
public uint cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SP_DEVICE_INTERFACE_DATA
{
public uint cbSize;
public Guid InterfaceClassGuid;
public uint Flags;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
private struct SP_DEVICE_INTERFACE_DETAIL_DATA // Only used for Marshal.SizeOf. NOT!
{
public uint cbSize;
public char DevicePath;
}
[DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false, ExactSpelling = true)]
private static extern uint CM_Get_Parent(out uint pdnDevInst, uint dnDevInst, uint ulFlags);
[DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern uint CM_Get_Device_ID(uint dnDevInst, string Buffer, uint BufferLen, uint ulFlags);
[DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false, ExactSpelling = true)]
private static extern uint CM_Get_Device_ID_Size(out uint pulLen, uint dnDevInst, uint ulFlags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs([In(), MarshalAs(UnmanagedType.LPStruct)] System.Guid ClassGuid, string Enumerator, IntPtr hwndParent, uint Flags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, [In()] ref SP_DEVINFO_DATA DeviceInfoData, [In(), MarshalAs(UnmanagedType.LPStruct)] System.Guid InterfaceClassGuid, uint MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, [In()] ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, uint DeviceInterfaceDetailDataSize, out uint RequiredSize, IntPtr DeviceInfoData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
private static extern int SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
private const uint DIGCF_PRESENT = 0x00000002U;
private const uint DIGCF_DEVICEINTERFACE = 0x00000010U;
private const int ERROR_INSUFFICIENT_BUFFER = 122;
private const uint CR_SUCCESS = 0;
private const int FILE_SHARE_READ = 1;
private const int FILE_SHARE_WRITE = 2;
private const uint GENERIC_READ = 0x80000000;
private const uint GENERIC_WRITE = 0x40000000;
private const int OPEN_EXISTING = 3;
private static readonly Guid GUID_PRINTER_INSTALL_CLASS = new Guid(0x4d36e979, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18);
private static readonly Guid GUID_DEVINTERFACE_USBPRINT = new Guid(0x28d78fad, 0x5a12, 0x11D1, 0xae, 0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2);
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
private static string GetPrinterRegistryInstanceID(string PrinterName) {
if (string.IsNullOrEmpty(PrinterName)) throw new ArgumentNullException("PrinterName");
const string key_template = #"SYSTEM\CurrentControlSet\Control\Print\Printers\{0}\PNPData";
using (var hk = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
string.Format(key_template, PrinterName),
Microsoft.Win32.RegistryKeyPermissionCheck.Default,
System.Security.AccessControl.RegistryRights.QueryValues
)
)
{
if (hk == null) throw new ArgumentOutOfRangeException("PrinterName", "This printer does not have PnP data.");
return (string)hk.GetValue("DeviceInstanceId");
}
}
private static string GetPrinterParentDeviceId(string RegistryInstanceID) {
if (string.IsNullOrEmpty(RegistryInstanceID)) throw new ArgumentNullException("RegistryInstanceID");
IntPtr hdi = SetupDiGetClassDevs(GUID_PRINTER_INSTALL_CLASS, RegistryInstanceID, IntPtr.Zero, DIGCF_PRESENT);
if (hdi.Equals(INVALID_HANDLE_VALUE)) throw new System.ComponentModel.Win32Exception();
try
{
SP_DEVINFO_DATA printer_data = new SP_DEVINFO_DATA();
printer_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
if (SetupDiEnumDeviceInfo(hdi, 0, ref printer_data) == 0) throw new System.ComponentModel.Win32Exception(); // Only one device in the set
uint cmret = 0;
uint parent_devinst = 0;
cmret = CM_Get_Parent(out parent_devinst, printer_data.DevInst, 0);
if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));
uint parent_device_id_size = 0;
cmret = CM_Get_Device_ID_Size(out parent_device_id_size, parent_devinst, 0);
if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get size of the device ID of the parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));
parent_device_id_size++; // To include the null character
string parent_device_id = new string('\0', (int)parent_device_id_size);
cmret = CM_Get_Device_ID(parent_devinst, parent_device_id, parent_device_id_size, 0);
if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get device ID of the parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));
return parent_device_id;
}
finally
{
SetupDiDestroyDeviceInfoList(hdi);
}
}
private static string GetUSBInterfacePath(string SystemDeviceInstanceID) {
if (string.IsNullOrEmpty(SystemDeviceInstanceID)) throw new ArgumentNullException("SystemDeviceInstanceID");
IntPtr hdi = SetupDiGetClassDevs(GUID_DEVINTERFACE_USBPRINT, SystemDeviceInstanceID, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hdi.Equals(INVALID_HANDLE_VALUE)) throw new System.ComponentModel.Win32Exception();
try
{
SP_DEVINFO_DATA device_data = new SP_DEVINFO_DATA();
device_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
if (SetupDiEnumDeviceInfo(hdi, 0, ref device_data) == 0) throw new System.ComponentModel.Win32Exception(); // Only one device in the set
SP_DEVICE_INTERFACE_DATA interface_data = new SP_DEVICE_INTERFACE_DATA();
interface_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));
if (SetupDiEnumDeviceInterfaces(hdi, ref device_data, GUID_DEVINTERFACE_USBPRINT, 0, ref interface_data) == 0) throw new System.ComponentModel.Win32Exception(); // Only one interface in the set
// Get required buffer size
uint required_size = 0;
SetupDiGetDeviceInterfaceDetail(hdi, ref interface_data, IntPtr.Zero, 0, out required_size, IntPtr.Zero);
int last_error_code = Marshal.GetLastWin32Error();
if (last_error_code != ERROR_INSUFFICIENT_BUFFER) throw new System.ComponentModel.Win32Exception(last_error_code);
IntPtr interface_detail_data = Marshal.AllocCoTaskMem((int)required_size);
try
{
// FIXME, don't know how to calculate the size.
// See https://stackoverflow.com/questions/10728644/properly-declare-sp-device-interface-detail-data-for-pinvoke
switch (IntPtr.Size)
{
case 4:
Marshal.WriteInt32(interface_detail_data, 4 + Marshal.SystemDefaultCharSize);
break;
case 8:
Marshal.WriteInt32(interface_detail_data, 8);
break;
default:
throw new NotSupportedException("Architecture not supported.");
}
if (SetupDiGetDeviceInterfaceDetail(hdi, ref interface_data, interface_detail_data, required_size, out required_size, IntPtr.Zero) == 0) throw new System.ComponentModel.Win32Exception();
// TODO: When upgrading to .NET 4, replace that with IntPtr.Add
return Marshal.PtrToStringAuto(new IntPtr(interface_detail_data.ToInt64() + Marshal.OffsetOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA), "DevicePath").ToInt64()));
}
finally
{
Marshal.FreeCoTaskMem(interface_detail_data);
}
}
finally
{
SetupDiDestroyDeviceInfoList(hdi);
}
}
public static string GetUSBPath(string PrinterName) {
return GetUSBInterfacePath(GetPrinterParentDeviceId(GetPrinterRegistryInstanceID(PrinterName)));
}
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenUSBPrinter(string PrinterName) {
return new Microsoft.Win32.SafeHandles.SafeFileHandle(CreateFile(GetUSBPath(PrinterName), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero), true);
}
}
Usage:
using (var sh = UsbPrinterResolver.OpenUSBPrinter("Zebra Large"))
{
using (var f = new System.IO.FileStream(sh, System.IO.FileAccess.ReadWrite))
{
// Read from and write to the stream f
}
}
Try this (Python code):
import _winreg
HKLM = _winreg.HKEY_LOCAL_MACHINE
#------------------------------------------------------------------------------
def getDevicePath(printerName):
key = _winreg.OpenKey(HKLM,
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\%s" \
% printerName)
value =_winreg.QueryValueEx(key, "Port")[0]
assert value.startswith("USB"), \
"Port does not start with 'USB': %s" % value
printerPortNumber = int(value.replace(u"USB", u""))
key = _winreg.OpenKey(HKLM,
r"SYSTEM\CurrentControlSet\Control\DeviceClasses" \
r"\{28d78fad-5a12-11d1-ae5b-0000f803a8c2}")
idx = 0
devicePath = None
while True:
try:
subKeyName = _winreg.EnumKey(key, idx)
subKey = _winreg.OpenKey(key, subKeyName)
try:
subSubKey = _winreg.OpenKey(subKey, r"#\Device Parameters")
baseName = _winreg.QueryValueEx(subSubKey, "Base Name")[0]
portNumber = _winreg.QueryValueEx(subSubKey, "Port Number")[0]
if baseName == "USB" and portNumber == printerPortNumber:
devicePath = subKeyName.replace("##?#USB", r"\\?\usb")
break
except WindowsError:
continue
finally:
idx += 1
except WindowsError:
break
return devicePath
Try this ... let me know if this helps ...
static void Main(string[] args)
{
ManagementObjectSearcher s = new ManagementObjectSearcher(#"Select * From Win32_PnPEntity");
foreach (ManagementObject device in s.Get())
{
// Try Name, Caption and/or Description (they seem to be same most of the time).
string Name = (string)device.GetPropertyValue("Name");
// >>>>>>>>>>>>>>>>>>>> Query String ...
if (Name == "O2Micro Integrated MMC/SD controller")
{
/*
class Win32_PnPEntity : CIM_LogicalDevice
{
uint16 Availability;
string Caption;
string ClassGuid;
string CompatibleID[];
uint32 ConfigManagerErrorCode;
boolean ConfigManagerUserConfig;
string CreationClassName;
string Description;
string DeviceID;
boolean ErrorCleared;
string ErrorDescription;
string HardwareID[];
datetime InstallDate;
uint32 LastErrorCode;
string Manufacturer;
string Name;
string PNPDeviceID;
uint16 PowerManagementCapabilities[];
boolean PowerManagementSupported;
string Service;
string Status;
uint16 StatusInfo;
string SystemCreationClassName;
string SystemName;
};
*/
try
{
Console.WriteLine("Name : {0}", Name);
Console.WriteLine("DeviceID : {0}", device.GetPropertyValue("DeviceID"));
Console.WriteLine("PNPDeviceID : {0}", device.GetPropertyValue("PNPDeviceID"));
Console.WriteLine("ClassGuid : {0}", device.GetPropertyValue("ClassGuid"));
Console.WriteLine("HardwareID :\n{0}", JoinStrings(device.GetPropertyValue("HardwareID") as string[]));
Console.WriteLine("CompatibleID :\n{0}", JoinStrings(device.GetPropertyValue("CompatibleID") as string[]));
}
catch (Exception e)
{
Console.WriteLine("ERROR: {0}", e.Message);
}
}
}
}
static string JoinStrings(string[] sarray)
{
StringBuilder b = new StringBuilder();
if (sarray != null)
{
foreach (string s in sarray)
b.Append(" '" + s + "'\n");
}
return b.ToString();
}
Don't have a USB printer to test against, but this provides the information you are looking for (including for USB devices)...
Description : O2Micro Integrated MMC/SD controller
DeviceID : PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05\4&26B31A7F&0&00E5
PNPDeviceID : PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05\4&26B31A7F&0&00E5
ClassGuid : {4d36e97b-e325-11ce-bfc1-08002be10318}
HardwareID :
'PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05'
'PCI\VEN_1217&DEV_8221&SUBSYS_04931028'
'PCI\VEN_1217&DEV_8221&CC_080501'
'PCI\VEN_1217&DEV_8221&CC_0805'
CompatibleID : 'PCI\VEN_1217&DEV_8221&REV_05'
'PCI\VEN_1217&DEV_8221'
'PCI\VEN_1217&CC_080501'
'PCI\VEN_1217&CC_0805'
'PCI\VEN_1217'
'PCI\CC_080501'
'PCI\CC_0805'
Also, for a URI, change the '\'s to '#'s in the URI you are intending of building.
so ..
usb\vid_0a5f&pid_0027\46a072900549\{28d78fad-5a12-11d1-ae5b-0000f803a8c2}
becomes
usb#vid_0a5f&pid_0027#46a072900549#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}
====
As GSerg pointed out that Win32_Printer Class helps with the above code, but doesn't provide the device id.
But if I use Win32_Printer class and print out the "PortName" property, that, for the printers I have installed gives be a port/filename that I can use with CreateFile() and open the device.
e.g.:
Name : Microsoft XPS Document Writer
Description :
DeviceID : Microsoft XPS Document Writer
PNPDeviceID :
PortName : XPSPort:
Name : Fax
Description :
DeviceID : Fax
PNPDeviceID :
PortName : SHRFAX:
Here, writing to "XPSPORT:" or "SHRFAX:" sends data to the printer. What does this do for your USB printer?
Use WinObj from Microsoft to get the specific device name. http://technet.microsoft.com/en-us/sysinternals/bb896657.aspx . This will quickly get you the proper device name to use with CreateFile to write directly to your USB printer or simply writing directly to a USB printer adapter with old school parallel port output for custom circuitry!
To open the port associated with a specific printer, you may need to use ntcreatefile. Use the EnumPrinters function to return a printer_info_2 structure containing the port name for each printer. This port name can then be opened with ntcreatefile (an NT internal version of CreateFile) which is explained here: http://msdn.microsoft.com/en-us/library/bb432380(v=vs.85).aspx
Why does this work? There are three namespace levels in windows NT file/device names and the port name retrieved from EnumPrinters can only be opened with ntcreatefile because it is only in the NT namespace. There may be an equivalent win32 namespace link for certain devices and roundabout ways to match them with a printer name but this is difficult as others have shown in prior answers.
Check out the Global?? folder in WinObj tool to show the symbolic links between win32 namespace and NT namespace on your machine. The old school COM1, COM2, LPT1, etc. device names are simply windows NT namespace symbolic links as well. Google "win32 nt namespace" for a more detailed explanation. (Sorry, but as a new user, I can only post 2 hyperlinks.)
I am not really a c++ guy, but I don't really think trying to generate the device id from the name is the way to go. However, you can
Use EnumPrinters and read the PRINTER_INFO_2 structure to get the driver name, and then read the driver details from registry like in this example.
Generate the name for yourself by finding out the printer details possibly from the PRINTER INFO structures and constructing it correctly. See http://msdn.microsoft.com/en-us/library/windows/hardware/ff553356(v=vs.85).aspx for details.
EDIT: You can actually get names + device instance ids of printers from registry:
HKLM\System\CurrentControlSet\Control\Print\Printers\

How to take the snapshot of a IE webpage through a BHO (C#)

I am trying to build an IE BHO in C# for taking the snapshot of a webpage loaded in the IE browser. Here is what I'm trying to do:
public class ShowToolbarBHO : BandObjectLib.IObjectWithSite
{
IWebBrowser2 webBrowser = null;
public void SetSite (Object site)
{
.......
if (site != null)
{
......
webBrowser = (IWebBrowser2)site;
......
}
}
}
Also, I p/invoke the following COM methods:
[Guid("0000010D-0000-0000-C000-000000000046")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
[ComImportAttribute()]
public interface IViewObject
{
void Draw([MarshalAs(UnmanagedType.U4)] int dwDrawAspect, int lindex, IntPtr pvAspect, [In] IntPtr ptd, IntPtr hdcTargetDev, IntPtr hdcDraw, [MarshalAs(UnmanagedType.LPStruct)] ref COMRECT lprcBounds, [In] IntPtr lprcWBounds, IntPtr pfnContinue, int dwContinue);
int GetColorSet([MarshalAs(UnmanagedType.U4)] int dwDrawAspect, int lindex, IntPtr pvAspect, [In] IntPtr ptd, IntPtr hicTargetDev, [Out] IntPtr ppColorSet);
int Freeze([MarshalAs(UnmanagedType.U4)] int dwDrawAspect, int lindex, IntPtr pvAspect, out IntPtr pdwFreeze);
int Unfreeze([MarshalAs(UnmanagedType.U4)] int dwFreeze);
int SetAdvise([MarshalAs(UnmanagedType.U4)] int aspects, [MarshalAs(UnmanagedType.U4)] int advf, [MarshalAs(UnmanagedType.Interface)] IAdviseSink pAdvSink);
void GetAdvise([MarshalAs(UnmanagedType.LPArray)] out int[] paspects, [MarshalAs(UnmanagedType.LPArray)] out int[] advf, [MarshalAs(UnmanagedType.LPArray)] out IAdviseSink[] pAdvSink);
}
[StructLayoutAttribute(LayoutKind.Sequential)]
public class COMRECT
{
public int left;
public int top;
public int right;
public int bottom;
public COMRECT()
{
}
public COMRECT(int left, int top, int right, int bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
}
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisibleAttribute(true)]
[GuidAttribute("0000010F-0000-0000-C000-000000000046")]
[ComImportAttribute()]
public interface IAdviseSink
{
void OnDataChange([In]IntPtr pFormatetc, [In]IntPtr pStgmed);
void OnViewChange([MarshalAs(UnmanagedType.U4)] int dwAspect, [MarshalAs(UnmanagedType.I4)] int lindex);
void OnRename([MarshalAs(UnmanagedType.Interface)] object pmk);
void OnSave();
void OnClose();
}
Now When I take the snapshot:
I make a call CaptureWebScreenImage((IHTMLDocument2) webBrowser.document);
public static Image CaptureWebScreenImage(IHTMLDocument2 myDoc) {
int heightsize = (int)getDocumentAttribute(myDoc, "scrollHeight");
int widthsize = (int)getDocumentAttribute(myDoc, "scrollWidth");
Bitmap finalImage = new Bitmap(widthsize, heightsize);
Graphics gFinal = Graphics.FromImage(finalImage);
COMRECT rect = new COMRECT();
rect.left = 0;
rect.top = 0;
rect.right = widthsize;
rect.bottom = heightsize;
IntPtr hDC = gFinal.GetHdc();
IViewObject vO = myDoc as IViewObject;
vO.Draw(1, -1, (IntPtr)0, (IntPtr)0, (IntPtr)0, (IntPtr)hDC, ref rect, (IntPtr)0, (IntPtr)0, 0);
gFinal.ReleaseHdc();
gFinal.Dispose();
return finalImage;
}
I am not getting the image of the webpage. Rather I am getting an image with black background. I am not sure if this is the right way of doing it, but I found over the web that IViewObject::Draw method is used for taking the image of a webpage in IE.
I was earlier doing the image capture using the Native PrintWindow() method as mentioned in the following codeproject's page - http://www.codeproject.com/KB/graphics/IECapture.aspx
But the image size is humongous! I was trying to see if I can reduce the size by using other techniques. It would be great if someone can point out the mistakes (I am sure there would be many) in my code above.
Thanks,
Kapil
You can try to query IHTMLElementRender interface for the document element (which is available through IHTMLDocument3 interface) and use DrawToDC method to render page to a bitmap.
1 IHTMLElementRender::DrawToDC is deprecated, according to Microsoft's documentation. And it does not draw object and canvas, like flash, as far as I can see.
It's better to cal IViewObject::Draw() or ::OleDraw().
2 I dont know how to do it in C#, but I know how to do it in win32 program. In win32 program, you need to call IOleInplaceObject::SetObjectRect to set IWebBrowser's position and size.
Or SetClientSite(), I am sure one of them does the magic.
CComQIPtr oleObject = browser;
CComQIPtr<IOleInPlaceObject> inPlaceObject(mOleObject);
inPlaceObject->SetObjectRects(&rcClient, &rcClient);
oleObject ->SetClientSite(clientSite);
Hope it helps

GetPhysicalMonitorsFromHMONITOR returned handle is always null

On the Media Foundation SDK there is the GetPhysicalMonitorsFromHMONITOR function
that I am trying to implement using C# but with no luck ...
In the returned PHYSICAL_MONITOR[], the function returns the string description of the monitor but for some mysterious reasons, the hPhysicalMonitor handle remains at 0.
I have generated the signatures with P/Invoke Interop Assistant with minor modifications.
Does the PHYSICAL_MONITOR structure or anything else needs further tuning ?
Thank you.
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using WindowsFormsApplication1;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public enum MC_DISPLAY_TECHNOLOGY_TYPE
{
MC_SHADOW_MASK_CATHODE_RAY_TUBE,
MC_APERTURE_GRILL_CATHODE_RAY_TUBE,
MC_THIN_FILM_TRANSISTOR,
MC_LIQUID_CRYSTAL_ON_SILICON,
MC_PLASMA,
MC_ORGANIC_LIGHT_EMITTING_DIODE,
MC_ELECTROLUMINESCENT,
MC_MICROELECTROMECHANICAL,
MC_FIELD_EMISSION_DEVICE,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct PHYSICAL_MONITOR
{
public IntPtr hPhysicalMonitor;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string szPhysicalMonitorDescription;
}
#region Imports
[DllImport("user32.dll", EntryPoint = "MonitorFromWindow")]
public static extern IntPtr MonitorFromWindow(
[In] IntPtr hwnd, uint dwFlags);
[DllImport("dxva2.dll", EntryPoint = "GetMonitorTechnologyType")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorTechnologyType(
IntPtr hMonitor, ref MC_DISPLAY_TECHNOLOGY_TYPE pdtyDisplayTechnologyType);
[DllImport("dxva2.dll", EntryPoint = "GetMonitorCapabilities")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorCapabilities(
IntPtr hMonitor, ref uint pdwMonitorCapabilities, ref uint pdwSupportedColorTemperatures);
[DllImport("dxva2.dll", EntryPoint = "DestroyPhysicalMonitors")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyPhysicalMonitors(
uint dwPhysicalMonitorArraySize, ref PHYSICAL_MONITOR[] pPhysicalMonitorArray);
[DllImport("dxva2.dll", EntryPoint = "GetNumberOfPhysicalMonitorsFromHMONITOR")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetNumberOfPhysicalMonitorsFromHMONITOR(
IntPtr hMonitor, ref uint pdwNumberOfPhysicalMonitors);
[DllImport("dxva2.dll", EntryPoint = "GetPhysicalMonitorsFromHMONITOR")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetPhysicalMonitorsFromHMONITOR(
IntPtr hMonitor, uint dwPhysicalMonitorArraySize, [Out] PHYSICAL_MONITOR[] pPhysicalMonitorArray);
#endregion
public Form1() { InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e)
{
// Get monitor handle.
uint dwFlags = 0u;
IntPtr ptr = MonitorFromWindow(Handle, dwFlags);
// Get number of physical monitors.
uint pdwNumberOfPhysicalMonitors = 0u;
bool b1 = GetNumberOfPhysicalMonitorsFromHMONITOR(ptr, ref pdwNumberOfPhysicalMonitors);
if (b1)
{
// Get physical monitors.
uint dwPhysicalMonitorArraySize = 0u;
dwPhysicalMonitorArraySize = pdwNumberOfPhysicalMonitors;
PHYSICAL_MONITOR[] pPhysicalMonitorArray = new PHYSICAL_MONITOR[dwPhysicalMonitorArraySize];
//NOTE : Handles remain null !
bool b2 = GetPhysicalMonitorsFromHMONITOR(ptr, dwPhysicalMonitorArraySize, pPhysicalMonitorArray);
if (pPhysicalMonitorArray[0].hPhysicalMonitor
== IntPtr.Zero)
{
throw new Exception("ERROR !");
}
// Monitor has capabilities to do that ?
if (b2)
{
uint pdwMonitorCapabilities = 0u;
uint pdwSupportedColorTemperatures = 0u;
bool b3 = GetMonitorCapabilities(
ptr, ref pdwMonitorCapabilities, ref pdwSupportedColorTemperatures);
// If yes, get technology type.
if (b3)
{
MC_DISPLAY_TECHNOLOGY_TYPE type = MC_DISPLAY_TECHNOLOGY_TYPE.MC_SHADOW_MASK_CATHODE_RAY_TUBE;
bool b4 = GetMonitorTechnologyType(ptr, ref type);
if (b4)
{
// Do work.
}
else
{
throw new Exception("Couldn't get monitor technology type.");
}
}
else
{
throw new Exception("Couldn't get monitor capabilities.");
}
}
else
{
throw new Exception("The monitor doesn't have the required capabilities.");
}
bool b5 = DestroyPhysicalMonitors(dwPhysicalMonitorArraySize, ref pPhysicalMonitorArray);
if (!b5)
{
throw new Exception("Couldn't destroy physical monitors.");
}
}
else
{
throw new Exception("Couldn't get number of physical monitors.");
}
}
}
}
Your statement:
The function returns the string description of the monitor but for some mysterious reasons, the hMonitor handle remains at 0.
is correct. If you look at the docs here, you'll see that hMonitor is clearly an [in] parameter and will not be changed.
Update following comment:
Sorry, didn't realize you meant the physical handle being returned in the structure. All the information I can find on that particular problem seems to indicate that your monitor probably isn't fully DDC/CI compatible (e.g., here).
All your structure definitions look fine to me, based on the docs on MSDN for that particular call. And indeed, it is populating the description for you.
What is the value for the number of physical monitors being returned from GetNumberOfPhysicalMonitorsFromHMONITOR (pdwNumberOfPhysicalMonitors)?
Also, what is the size of your PHYSICAL_MONITOR structure and are you running in 32 or 64 bits?
It is alright that the hPhysicalMonitor value is 0. However, in the question's code sample all calls after the GetPhysicalMonitorsFromHMONITOR should use the hPhysicalMonitor reference instead of the ptr reference. The updated Form_Load method should be the following:
private void Form1_Load(object sender, EventArgs e)
{
// Get monitor handle.
uint dwFlags = 0u;
IntPtr ptr = MonitorFromWindow(Handle, dwFlags);
// Get number of physical monitors.
uint pdwNumberOfPhysicalMonitors = 0u;
bool b1 = GetNumberOfPhysicalMonitorsFromHMONITOR(ptr, ref pdwNumberOfPhysicalMonitors);
if (b1)
{
// Get physical monitors.
uint dwPhysicalMonitorArraySize = 0u;
dwPhysicalMonitorArraySize = pdwNumberOfPhysicalMonitors;
PHYSICAL_MONITOR[] pPhysicalMonitorArray = new PHYSICAL_MONITOR[dwPhysicalMonitorArraySize];
//NOTE : Handles remain null !
bool b2 = GetPhysicalMonitorsFromHMONITOR(ptr, dwPhysicalMonitorArraySize, pPhysicalMonitorArray);
// Monitor has capabilities to do that ?
if (b2)
{
uint pdwMonitorCapabilities = 0u;
uint pdwSupportedColorTemperatures = 0u;
bool b3 = GetMonitorCapabilities(pPhysicalMonitorArray[0].hPhysicalMonitor, ref pdwMonitorCapabilities, ref pdwSupportedColorTemperatures);
// If yes, get technology type.
if (b3)
{
MC_DISPLAY_TECHNOLOGY_TYPE type = MC_DISPLAY_TECHNOLOGY_TYPE.MC_SHADOW_MASK_CATHODE_RAY_TUBE;
bool b4 = GetMonitorTechnologyType(pPhysicalMonitorArray[0].hPhysicalMonitor, ref type);
if (b4)
{
// Do work.
}
else
{
throw new Exception("Couldn't get monitor technology type.");
}
}
else
{
throw new Exception("Couldn't get monitor capabilities.");
}
}
else
{
throw new Exception("The monitor doesn't have the required capabilities.");
}
bool b5 = DestroyPhysicalMonitors(dwPhysicalMonitorArraySize, ref pPhysicalMonitorArray);
if (!b5)
{
throw new Exception("Couldn't destroy physical monitors.");
}
}
else
{
throw new Exception("Couldn't get number of physical monitors.");
}
}
The monitor supports this function because with software like softMCCS and WinI2C/DDC,
the properties are returned correctly.
The return pdwNumberOfPhysicalMonitors value is 1 which is correct.
As you can see, its size is pdwNumberOfPhysicalMonitors :
PHYSICAL_MONITOR[] pPhysicalMonitorArray = new PHYSICAL_MONITOR[dwPhysicalMonitorArraySize];
And I am running Vista 32.
It is somewhat strange because half of it works, that's now about 4 days I am over it but still no progress ...
Thank you.

Resources