Running a process at the Windows 7 Welcome Screen - windows-7
So here's the scoop:
I wrote a tiny C# app a while back that displays the hostname, ip address, imaged date, thaw status (we use DeepFreeze), current domain, and the current date/time, to display on the welcome screen of our Windows 7 lab machines. This was to replace our previous information block, which was set statically at startup and actually embedded text into the background, with something a little more dynamic and functional. The app uses a Timer to update the ip address, deepfreeze status, and clock every second, and it checks to see if a user has logged in and kills itself when it detects such a condition.
If we just run it, via our startup script (set via group policy), it holds the script open and the machine never makes it to the login prompt. If we use something like the start or cmd commands to start it off under a separate shell/process, it runs until the startup script finishes, at which point Windows seems to clean up any and all child processes of the script. We're currently able to bypass that using psexec -s -d -i -x to fire it off, which lets it persist after the startup script is completed, but can be incredibly slow, adding anywhere between 5 seconds and over a minute to our startup time.
We have experimented with using another C# app to start the process, via the Process class, using WMI Calls (Win32_Process and Win32_ProcessStartup) with various startup flags, etc, but all end with the same result of the script finishing and the info block process getting killed. I tinkered with rewriting the app as a service, but services were never designed to interact with the desktop, let alone the login window, and getting things operating in the right context never really seemed to work out.
So for the question: Does anybody have a good way to accomplish this? Launch a task so that it would be independent of the startup script and run on top of the welcome screen?
This can be done through a lot of Win32 API calls. I have managed to get a program with a GUI onto the Winlogon desktop (before anyone asks, it's not an interactive GUI). Basically you need to run a loader process as SYSTEM, which will then spawn the new process. Since you most likely want this process to run on start up, you can either use the task scheduler to run the loader as SYSTEM or you can use a service to do the same thing. I'm currently using a service, but I tried using the task scheduler and it did work just fine.
Short summary:
Grab the Winlogon.exe process (as a Process)
Grab the token of winlogon using OpenProcessToken using the .handle of the Process
Create a new token and duplicate the winlogon token to it
Elevate the privileges of the token
Create the process using CreateProcessAsUser, making sure to set lpDesktop to "Winsta0\Winlogon" and using the token you created.
Code example:
// grab the winlogon process
Process winLogon = null;
foreach (Process p in Process.GetProcesses()) {
if (p.ProcessName.Contains("winlogon")) {
winLogon = p;
break;
}
}
// grab the winlogon's token
IntPtr userToken = IntPtr.Zero;
if (!OpenProcessToken(winLogon.Handle, TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE, out userToken)) {
log("ERROR: OpenProcessToken returned false - " + Marshal.GetLastWin32Error());
}
// create a new token
IntPtr newToken = IntPtr.Zero;
SECURITY_ATTRIBUTES tokenAttributes = new SECURITY_ATTRIBUTES();
tokenAttributes.nLength = Marshal.SizeOf(tokenAttributes);
SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
threadAttributes.nLength = Marshal.SizeOf(threadAttributes);
// duplicate the winlogon token to the new token
if (!DuplicateTokenEx(userToken, 0x10000000, ref tokenAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenImpersonation, out newToken)) {
log("ERROR: DuplicateTokenEx returned false - " + Marshal.GetLastWin32Error());
}
TOKEN_PRIVILEGES tokPrivs = new TOKEN_PRIVILEGES();
tokPrivs.PrivilegeCount = 1;
LUID seDebugNameValue = new LUID();
if (!LookupPrivilegeValue(null, SE_DEBUG_NAME, out seDebugNameValue)) {
log("ERROR: LookupPrivilegeValue returned false - " + Marshal.GetLastWin32Error());
}
tokPrivs.Privileges = new LUID_AND_ATTRIBUTES[1];
tokPrivs.Privileges[0].Luid = seDebugNameValue;
tokPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// escalate the new token's privileges
if (!AdjustTokenPrivileges(newToken, false, ref tokPrivs, 0, IntPtr.Zero, IntPtr.Zero)) {
log("ERROR: AdjustTokenPrivileges returned false - " + Marshal.GetLastWin32Error());
}
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "Winsta0\\Winlogon";
// start the process using the new token
if (!CreateProcessAsUser(newToken, process, process, ref tokenAttributes, ref threadAttributes,
true, (uint)CreateProcessFlags.CREATE_NEW_CONSOLE | (uint)CreateProcessFlags.INHERIT_CALLER_PRIORITY, IntPtr.Zero,
logInfoDir, ref si, out pi)) {
log("ERROR: CreateProcessAsUser returned false - " + Marshal.GetLastWin32Error());
}
Process _p = Process.GetProcessById(pi.dwProcessId);
if (_p != null) {
log("Process " + _p.Id + " Name " + _p.ProcessName);
} else {
log("Process not found");
}
This is one of those "You really need a good reason to do this" questions. Microsoft tries very hard to block applications running at the startup screen - every bit of code in Windows which interacts with the logon screen is very carefully code reviewed because the security consequences of a bug in code running at the logon screen are dire - if you screw up even slightly, you'll allow malware to get onto the computer.
Why do you want to run your program at the logon screen? Maybe there's a documented way of doing it that's not as risky.
I translated the code above in C++, if someone else needs it... Notice there are references to parts of my code, but it may help anyway:
static bool StartProcess(LPCTSTR lpApplicationPath)
{
CAutoGeneralHandle hWinlogonProcess = FindWinlogonProcess();
if (hWinlogonProcess == INVALID_HANDLE_VALUE)
{
DU_OutputDebugStringff(L"ERROR: Can't find the 'winlogon' process");
return false;
}
CAutoGeneralHandle hUserToken;
if (!OpenProcessToken(hWinlogonProcess, TOKEN_QUERY|TOKEN_IMPERSONATE|TOKEN_DUPLICATE, &hUserToken))
{
DU_OutputDebugStringff(L"ERROR: OpenProcessToken returned false (error %u)", GetLastError());
return false;
}
// Create a new token
SECURITY_ATTRIBUTES tokenAttributes = {0};
tokenAttributes.nLength = sizeof tokenAttributes;
SECURITY_ATTRIBUTES threadAttributes = {0};
threadAttributes.nLength = sizeof threadAttributes;
// Duplicate the winlogon token to the new token
CAutoGeneralHandle hNewToken;
if (!DuplicateTokenEx(hUserToken, 0x10000000, &tokenAttributes,
SECURITY_IMPERSONATION_LEVEL::SecurityImpersonation,
TOKEN_TYPE::TokenImpersonation, &hNewToken))
{
DU_OutputDebugStringff(L"ERROR: DuplicateTokenEx returned false (error %u)", GetLastError());
return false;
}
TOKEN_PRIVILEGES tokPrivs = {0};
tokPrivs.PrivilegeCount = 1;
LUID seDebugNameValue = {0};
if (!LookupPrivilegeValue(nullptr, SE_DEBUG_NAME, &seDebugNameValue))
{
DU_OutputDebugStringff(L"ERROR: LookupPrivilegeValue returned false (error %u)", GetLastError());
return false;
}
tokPrivs.Privileges[0].Luid = seDebugNameValue;
tokPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Escalate the new token's privileges
if (!AdjustTokenPrivileges(hNewToken, false, &tokPrivs, 0, nullptr, nullptr))
{
DU_OutputDebugStringff(L"ERROR: AdjustTokenPrivileges returned false (error %u)", GetLastError());
return false;
}
PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0};
si.cb = sizeof si;
si.lpDesktop = L"Winsta0\\Winlogon";
// Start the process using the new token
if (!CreateProcessAsUser(hNewToken, lpApplicationPath, nullptr, &tokenAttributes, &threadAttributes,
true, CREATE_NEW_CONSOLE|INHERIT_CALLER_PRIORITY, nullptr, nullptr, &si, &pi))
{
DU_OutputDebugStringff(L"ERROR: CreateProcessAsUser returned false (error %u)", GetLastError());
return false;
}
return true;
}
I think you can do it, but it's pretty involved. Interactive apps aren't normally allowed to run on the welcome screen. At a high level, you'll need to:
Create a windows service that starts automatically
Use the windows service to create another process on the current session and desktop (using the Win32 methods WTSGetActiveConsoleSessionId and OpenInputDesktop)
I wrote an app that can interact somewhat with the login screen, but it doesn't show any UI. It probably can be done, but it may be even more involved.
Note: I found that I was unable to get results from OpenInputDesktop from my Windows service. I had to instead make the call in the other process and notify the service to restart the process on the correct desktop.
I hope that can at least get you started. Good luck!
Ignoring pre-Vista OS's, assuming you have TCB privs on your token (are running as System, basically), you can use CreateProcessAsUser to do this.
Example to be run as System (e.g.: an NT Service or with psexec -s) which will start notepad in the console session winlogon desktop:
#define WIN32_LEAN_AND_MEAN
#pragma comment(lib, "Userenv.lib")
#include <Windows.h>
#include <UserEnv.h>
#include <iostream>
#include <string>
HANDLE GetTokenForStart();
LPVOID GetEnvBlockForUser(HANDLE hToken);
void StartTheProcess(HANDLE hToken, LPVOID pEnvironment);
int main(int argc, wchar_t* argv[])
{
//while (!IsDebuggerPresent()) Sleep(500);
try
{
HANDLE hUserToken = GetTokenForStart();
LPVOID env = GetEnvBlockForUser(hUserToken);
StartTheProcess(hUserToken, env);
}
catch (std::wstring err)
{
auto gle = GetLastError();
std::wcerr << L"Error: " << err << L" GLE: " << gle << L"\r\n";
return -1;
}
}
HANDLE GetTokenForStart()
{
HANDLE hToken = 0;
{
HANDLE processToken = 0;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE, &processToken))
{
throw std::wstring(L"Could not open current process token");
}
if (!DuplicateTokenEx(processToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hToken))
{
throw std::wstring(L"Could not duplicate process token");
}
}
DWORD consoleSessionId = WTSGetActiveConsoleSessionId();
if (!SetTokenInformation(hToken, TokenSessionId, &consoleSessionId, sizeof(consoleSessionId)))
{
throw std::wstring(L"Could not set session ID");
}
return hToken;
}
LPVOID GetEnvBlockForUser(HANDLE hToken)
{
LPVOID pEnvironment = NULL;
if (!CreateEnvironmentBlock(&pEnvironment, hToken, FALSE))
{
throw std::wstring(L"Could not create env block");
}
return pEnvironment;
}
void StartTheProcess(HANDLE hToken, LPVOID pEnvironment)
{
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
si.lpDesktop = (LPWSTR)L"winsta0\\winlogon";
wchar_t path[MAX_PATH] = L"notepad.exe";
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcessAsUser(hToken, NULL, path, NULL, NULL, FALSE,
CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &pi))
{
throw std::wstring(L"Could not start process");
}
if (!CloseHandle(pi.hThread))
{
throw std::wstring(L"Could not close thread handle");
}
}
Or, if you prefer C#:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
namespace StartWinlogonManaged
{
class Program
{
static void Main(string[] args)
{
var hUserToken = GetTokenForStart();
var env = GetEnvBlockForUser(hUserToken);
StartTheProcess(hUserToken, env);
}
const string
Advapi32 = "advapi32.dll",
Userenv = "userenv.dll",
Kernel32 = "kernel32.dll";
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetCurrentProcess();
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
public static extern bool OpenProcessToken(IntPtr ProcessToken, int DesiredAccess, out IntPtr TokenHandle);
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
public static extern bool DuplicateTokenEx(IntPtr ExistingToken, int DesiredAccess,
IntPtr TokenAttributes, int ImpersonationLevel, int TokenType, out IntPtr NewToken);
[DllImport("kernel32.dll", ExactSpelling = true)]
static extern int WTSGetActiveConsoleSessionId();
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetTokenInformation(IntPtr hToken,
int tokenInfoClass, ref int pTokenInfo, int tokenInfoLength);
static IntPtr GetTokenForStart()
{
IntPtr hToken = IntPtr.Zero;
{
IntPtr processToken = IntPtr.Zero;
if (!OpenProcessToken(GetCurrentProcess(), 0x2001f /* TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE */, out processToken))
{
throw new Win32Exception("Could not open current process token");
}
if (!DuplicateTokenEx(processToken, 0x02000000 /* MAXIMUM_ALLOWED */, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken))
{
throw new Win32Exception("Could not duplicate process token");
}
}
int consoleSessionId = WTSGetActiveConsoleSessionId();
if (!SetTokenInformation(hToken, 12 /* TokenSessionId */, ref consoleSessionId, 4 /* sizeof(int) */))
{
throw new Win32Exception("Could not set session ID");
}
return hToken;
}
[DllImport(Userenv, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
static IntPtr GetEnvBlockForUser(IntPtr hToken)
{
IntPtr pEnvironment = IntPtr.Zero;
if (!CreateEnvironmentBlock(out pEnvironment, hToken, true))
{
throw new Win32Exception("Could not create env block");
}
return pEnvironment;
}
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessAsUser(IntPtr hToken,
StringBuilder appExeName, StringBuilder commandLine, IntPtr processAttributes,
IntPtr threadAttributes, bool inheritHandles, uint dwCreationFlags,
IntPtr environment, string currentDirectory, ref STARTUPINFO startupInfo,
out PROCESS_INFORMATION startupInformation);
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int cb;
public IntPtr lpReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpDesktop;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);
static void StartTheProcess(IntPtr hToken, IntPtr pEnvironment)
{
var si = new STARTUPINFO();
si.cb = Marshal.SizeOf<STARTUPINFO>();
si.dwFlags = 1 /* STARTF_USESHOWWINDOW */;
si.wShowWindow = 5 /* SW_SHOW */;
si.lpDesktop = "winsta0\\winlogon";
var path = new StringBuilder("notepad.exe", 260);
PROCESS_INFORMATION pi;
if (!CreateProcessAsUser(hToken, null, path, IntPtr.Zero, IntPtr.Zero, false,
0x410 /* CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT */, pEnvironment, null, ref si, out pi))
{
throw new Win32Exception("Could not start process");
}
if (!CloseHandle(pi.hThread))
{
throw new Win32Exception("Could not close thread handle");
}
}
}
}
Note that this does require several privileges (TCB, AssignPrimaryToken, IncreaseQuota) enabled in your token. This code also leaks handles, does not formulate a full command line, use name constants, etc..., and is only intended as an expository reference - not as a ready solution.
Related
I run an EXE program from a Windows Service but I can't see form C#?
I run another program from Windows Service private Process RunApp(string _appLocation) { //Create Process Process _ret = new Process(); _ret.StartInfo.FileName = _appLocation; //Run as Administrator. _ret.StartInfo.Verb = "runas"; //Window = normal _ret.StartInfo.WindowStyle = ProcessWindowStyle.Normal; _ret.StartInfo.CreateNoWindow = false; _ret.Start(); return _ret; } I see it in Task Manager. it is working but I can't see window. (If I can not see this then I can change Parent and see it)I try this: Create new MidiChildWindow project and: [DllImport("user32.dll", SetLastError = true)] static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll")] static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); private void GetListeners() { Process[] Memory = Process.GetProcesses(); foreach (Process _prc in Memory) { if (_prc.ProcessName == "MyRunnedApplication") { SetParent(_prc.MainWindowHandle, this.Handle); ShowWindow(_prc.MainWindowHandle, 1); } } } but its not work. Runned my application is child the Windows service and I can not change it. How I see it? (Sory my bad english)
RunProcessAsUser causing problems
I have an application that is being started by a Service automatically when the user logs on to his account. The application in question is a Winforms application and the problem is that sometimes when the application is started from the service it inexplicably hangs. Killing it and running it once more without a service works fine. I am not sure why this is happening and why it only happens occasionally (not all the time). I always check the new process information and it is always running under the correct session of the logged in user. I am not sure if I am doing something wrong when impersonating the user but in any case I am listing the code just so that maybe someone can find a flaw in it: public int RunWithTokenInSession(IntPtr token, int nSessionID) { IntPtr primaryToken = GetUserTokenOfSessoin((uint)nSessionID); if (primaryToken == IntPtr.Zero) { return -1; } STARTUPINFO StartupInfo = new STARTUPINFO(); processInfo_ = new PROCESS_INFORMATION(); StartupInfo.cb = Marshal.SizeOf(StartupInfo); //StartupInfo.wShowWindow = (short)ShowCommands.SW_HIDE; StartupInfo.lpDesktop = "Winsta0\\Default"; SECURITY_ATTRIBUTES Security1 = new SECURITY_ATTRIBUTES(); SECURITY_ATTRIBUTES Security2 = new SECURITY_ATTRIBUTES(); string command = "\"" + processPath_ + "\""; if ((arguments_ != null) && (arguments_.Length != 0)) { command += " " + arguments_; } IntPtr lpEnvironment = IntPtr.Zero; bool resultEnv = CreateEnvironmentBlock(out lpEnvironment, primaryToken, false); if (resultEnv != true) { int nError = GetLastError(); } bool b = CreateProcessAsUser(primaryToken, null, command, ref Security1, ref Security2, false, /*CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS |*/ CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS, lpEnvironment, null, ref StartupInfo, out processInfo_); int error = 0; if (!b) { error = Marshal.GetLastWin32Error(); string message = String.Format("CreateProcessAsUser Error: {0}", error); Debug.WriteLine(message); } DestroyEnvironmentBlock(lpEnvironment); CloseHandle(primaryToken); return error; } And this is the code for GetUserTokenOfSessoin: public static IntPtr GetUserTokenOfSessoin(uint dwSessionId) { IntPtr currentToken = IntPtr.Zero; IntPtr primaryToken = IntPtr.Zero; IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; IntPtr hUserToken = IntPtr.Zero; IntPtr hTokenDup = IntPtr.Zero; bool bRet = WTSQueryUserToken(dwSessionId, out currentToken); if (bRet == false) { int n = GetLastError(); return IntPtr.Zero; } bool fInAdminGroup = false; //IntPtr hToken = IntPtr.Zero; IntPtr hTokenToCheck = IntPtr.Zero; IntPtr pElevationType = IntPtr.Zero; IntPtr pLinkedToken = IntPtr.Zero; int cbSize = 0; if (Environment.OSVersion.Version.Major >= 6) { // Running Windows Vista or later (major version >= 6). // Determine token type: limited, elevated, or default. // Allocate a buffer for the elevation type information. cbSize = sizeof(TOKEN_ELEVATION_TYPE); pElevationType = Marshal.AllocHGlobal(cbSize); if (pElevationType == IntPtr.Zero) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } // Retrieve token elevation type information. if (!GetTokenInformation(currentToken, TOKEN_INFORMATION_CLASS.TokenElevationType, pElevationType, cbSize, out cbSize)) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } // Marshal the TOKEN_ELEVATION_TYPE enum from native to .NET. TOKEN_ELEVATION_TYPE elevType = (TOKEN_ELEVATION_TYPE)Marshal.ReadInt32(pElevationType); // If limited, get the linked elevated token for further check. if (elevType == TOKEN_ELEVATION_TYPE.TokenElevationTypeLimited) { // Allocate a buffer for the linked token. cbSize = IntPtr.Size; pLinkedToken = Marshal.AllocHGlobal(cbSize); if (pLinkedToken == IntPtr.Zero) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } // Get the linked token. if (!GetTokenInformation(currentToken, TOKEN_INFORMATION_CLASS.TokenLinkedToken, pLinkedToken, cbSize, out cbSize)) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } // Marshal the linked token value from native to .NET. hTokenToCheck = Marshal.ReadIntPtr(pLinkedToken); bRet = DuplicateTokenEx(hTokenToCheck, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken); } else { bRet = DuplicateTokenEx(currentToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken); } } else { // XP bRet = DuplicateTokenEx(currentToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken); } if (bRet == false) { int lastError = Marshal.GetLastWin32Error(); return IntPtr.Zero; } return primaryToken; } Now the issue seems to exclusively happen on Windows Server 2008 R2 as I haven't been able to reproduce it on any other version of Windows so I was wondering if there is anything special about Server 2008 and do I need to take extra security measures or obtain an extra privilege to run the process as the user.
Can DevCon notify when a driver is finished installing after a rescan?
I am trying to install a driver during a windows-setup project. The first step I do is to copy the INF file and preinstall the driver. SetupCopyOEMInf(infFile, null, 1, 0, null, 0, 0, null); This correctly preinstalls the driver, but the device is not ready to use until a hardware rescan is done in Device Manager. I want to automate this as well. I have tried using the setupapi.dll to invoke the hardware rescan, but it was not always successful for me. Using devcon.exe rescan always forces the hardware rescan, but it is a synchronous command, and it returns before the device is finished installing. Is there any way to get a return result after the hardware scan completes and the driver is successfully installed? Thanks, Misha Edit Here is my working code: public const UInt32 CR_SUCCESS = 0; public const UInt64 CM_REENUMERATE_SYNCHRONOUS = 1; public const UInt64 CM_LOCATE_DEVNODE_NORMAL = 0; [DllImport("setupapi.dll")] public static extern bool SetupCopyOEMInf( string SourceInfFileName, string OEMSourceMediaLocation, int OEMSourceMediaType, int CopyStyle, string DestinationInfFileName, int DestinationInfFileNameSize, int RequiredSize, string DestinationInfFileNameComponent ); [DllImport("cfgmgr32.dll")] public static extern int CM_Locate_DevNode_Ex(ref IntPtr deviceHandle, int deviceId, uint flags, IntPtr machineHandle); [DllImport("cfgmgr32.dll")] public static extern int CM_Reenumerate_DevNode_Ex(IntPtr devInst, UInt64 flags); [DllImport("cfgmgr32.dll")] public static extern int CMP_WaitNoPendingInstallEvents(UInt32 timeOut); static void Main() { bool success = SetupCopyOEMInf(infFile, null, 1, 0, null, 0, 0, null); if(!success) { throw new Exception("Error installing driver"); } success = RescanAllDevices(); if (!success) { throw new Exception("Error installing driver"); } } public static bool RescanAllDevices() { int ResultCode = 0; IntPtr LocalMachineInstance = IntPtr.Zero; IntPtr DeviceInstance = IntPtr.Zero; UInt32 PendingTime = 30000; ResultCode = CM_Locate_DevNode_Ex(ref DeviceInstance, 0, 0, LocalMachineInstance); if (CR_SUCCESS == ResultCode) { ResultCode = CM_Reenumerate_DevNode_Ex(DeviceInstance, CM_REENUMERATE_SYNCHRONOUS); ResultCode = CMP_WaitNoPendingInstallEvents(PendingTime); } return ResultCode == CR_SUCCESS; }
The source for devcon is available in the WDK. It's in the src\setup\devcon directory. The logic for the rescan command is in the cmdRescan function in cmds.cpp. It would be a simple matter to copy that logic into your own code and make sure it doesn't return immediately.
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\
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.