Related
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)
I searched but didn't find anything that specifically addresses this question.
Consider the code, can the new IE window be created in front of all other open windows? VBScript had the Run Method (Windows Script Host) that offered intWindowStyle
Optional. Integer value indicating the appearance of the program's window. Note that not all programs make use of this information.
Does Powershell's new com object have anything similar?
Param(
[Parameter(Mandatory=$false)]
[string]$svr,
[Parameter(Mandatory=$false)]
[string]$last,
[Parameter(Mandatory=$false)]
[string]$desc,
[Parameter(Mandatory=$false)]
[string]$date)
$ie = new-object -comobject InternetExplorer.Application
$ie.navigate("http://www.stackoverflow.com")
# Wait for the page to finish loading
do {sleep 1} until (-not ($ie.Busy))
$doc = $ie.document
$link = $doc.getElementById("Sym_Msg").Value = "$svr"
$link = $doc.getElementById("Lname").Value = "$last"
$link = $doc.getElementById("Desc").Value = "$desc"
$link = $doc.getElementById("Date_Ent").Value = "$date"
$button = $doc.getElementById("submit1")
$ie.Visible = $true
$button.click();
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ie) | Out-Null
Unfortunately I don’t think this is part of ‘new-object’. There are several PowerShell extensions that implement this as cmdlet (Such as http://pscx.codeplex.com/ "Set-ForegroundWindow").
The way this is implemented is a call to "user32.dll".
In your code this should work:
#Load DLL
$pinvoke = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow)'
Add-Type -MemberDefinition $pinvoke -name NativeMethods -namespace Win32
#Get WindowHandle of the COM Object
$hwnd = $ie.HWND
# Minimize window
[Win32.NativeMethods]::ShowWindowAsync($hwnd, 2)
# Restore window
[Win32.NativeMethods]::ShowWindowAsync($hwnd, 4)
--Edited to add ' after nCmdShow)
This was incredibly helpful for me. I needed to do the same with a windows form, not IE, and so I needed to do a few tweaks. I borrowed the form creation code from someone else on the internet.
This works great when the SCCM client executes a script-based installer.
In particular I had to add the following at the end, and tweak a few other things:
$appContext = New-Object System.Windows.Forms.ApplicationContext
[void][System.Windows.Forms.Application]::Run($appContext)
Here's the code I used:
Add-Type -AssemblyName PresentationFramework, System.Drawing, System.Windows.Forms, WindowsFormsIntegration
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Test"
$objForm.Size = New-Object System.Drawing.Size(400,200)
$objForm.StartPosition = "CenterScreen"
$objform.icon = "$StrInstDir\source\favicon.ico"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$output = "OK";$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$output = "Cancel";$objForm.Close()}})
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(105,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$global:output="OK";$objForm.Close()})
$objForm.Controls.Add($OKButton)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(220,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$global:output = "Cancel";$objForm.Close()})
$objForm.Controls.Add($CancelButton)
$objform.Add_Closing({[System.Windows.Forms.Application]::Exit()})
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(60,20)
$objLabel.Size = New-Object System.Drawing.Size(280,200)
$objLabel.Text = "Test Text"
$objForm.Controls.Add($objLabel)
[void] $objForm.Show()
$objForm.Add_Shown({$objForm.Activate()})
$signature = #"
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public static IntPtr FindWindow(string windowName){
return FindWindow(null,windowName);
}
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd,
IntPtr hWndInsertAfter, int X,int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;
public static void MakeTopMost (IntPtr fHandle)
{
SetWindowPos(fHandle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS);
}
public static void MakeNormal (IntPtr fHandle)
{
SetWindowPos(fHandle, HWND_NOTOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS);
}
"#
$hWnd = $objform.handle
$app = Add-Type -MemberDefinition $signature -Name Win32Window -Namespace ScriptFanatic.WinAPI -ReferencedAssemblies System.Windows.Forms -Using System.Windows.Forms -PassThru
$null = $app::MakeTopMost($hWnd)
$objform.Visible = $true
$appContext = New-Object System.Windows.Forms.ApplicationContext
[void][System.Windows.Forms.Application]::Run($appContext)
For future reference, the only way to make a window topmost involves some P/Invoke digging into Win32 APIs. Fun stuff, especially from PowerShell!
Add-Type -Namespace PInvoke -Name SWP '[DllImport("user32.dll", SetLastError=true)] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);'
[PInvoke.SWP]::SetWindowPos($hWnd, -1, 0, 0, 0, 0, 3)
This code was cobbled together from this answer, Lee Holmes' old blog post on P/Invoke in PoSh, and the pinvoke.net/MSDN docs.
Finally with the pointer from #Nathan Tuggy, I discovered this page which ends up working great. The new IE window is created then brought to the front of all open windows.
Summary of code in PowerShell to create a new IE ComObject and bring it to the front.
$ie = New-Object -ComObject InternetExplorer.Application
$ie.navigate("http://www.stackoverflow.com")
do {sleep 1} until (-not ($ie.Busy))
$signature = #"
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public static IntPtr FindWindow(string windowName){
return FindWindow(null,windowName);
}
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd,
IntPtr hWndInsertAfter, int X,int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;
public static void MakeTopMost (IntPtr fHandle)
{
SetWindowPos(fHandle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS);
}
public static void MakeNormal (IntPtr fHandle)
{
SetWindowPos(fHandle, HWND_NOTOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS);
}
"#
$hWnd = $ie.HWND
$app = Add-Type -MemberDefinition $signature -Name Win32Window -Namespace ScriptFanatic.WinAPI -ReferencedAssemblies System.Windows.Forms -Using System.Windows.Forms -PassThru
$null = $app::MakeTopMost($hWnd)
$ie.Visible = $true
$button.click();
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ie) | Out-Null
I'm developing a USB device as a standard HID keyboard with an 8-byte feature report added to the report descriptor; I'm writing an host app to configure the device. I'm trying to adapt the wonderful HidLibrary to utilize the HidD_GetFeature() function in hid.dll.
Before I start posting c# code, I will say that I have successfully tested my firmware using the SimpleHidWrite utility with both Get and Set Feature commands, so I'm fairly confident that's not the issue.
The HidD_SetFeature() function, as wrapped in the HidLibrary API, works great. I can write 8 bytes to the device and I've verified using the SimpleHidWrite tool that they are stored correctly. However, I can't pull those bytes back using HidD_GetFeature(), and I'm baffled as to why.
Here are what I believe to be the pertinent details.
First, the CreateFile call built into the library with values:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static IntPtr CreateFile(
string lpFileName,
uint dwDesiredAccess,
int dwShareMode,
ref HidLibrary.NativeMethods.SECURITY_ATTRIBUTES lpSecurityAttributes,
int dwCreationDisposition,
int dwFlagsAndAttributes,
int hTemplateFile);
where (using test vid/pid):
lpFileName = "\\?\hid#vid_03eb&pid_2042#7&1fef463f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"
dwDesiredAccess = 0
dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE (0x01 | 0x02)
lpSecurityAttributes = { bInheritHandle = true, lpSecurityDescriptor = 0, nLength = 12 }
dwCreationDisposition = OPEN_EXISTING (3)
dwFlagsAndAttributes = 0
hTemplateFile = 0
The import definition:
[DllImport("hid.dll", SetLastError = true)]
static internal extern bool HidD_GetFeature(IntPtr hidDeviceObject, ref byte[] lpReportBuffer, int reportBufferLength);
And finally the API method I'm creating that is currently failing on the HidD_GetFeature() call with error 23 (ERROR_CRC):
public bool ReadFeatureData(byte reportId, out byte[] data)
{
if (_deviceCapabilities.FeatureReportByteLength <= 0)
{
data = new byte[0];
return false;
}
// FeatureReportByteLength returns 9 (byte 0 is the report id and 8 bytes for the actual report length)
data = new byte[_deviceCapabilities.FeatureReportByteLength];
//yields a 9-byte array
var buffer = this.CreateFeatureInputBuffer();
buffer[0] = reportId;
IntPtr hidHandle = IntPtr.Zero;
bool success = false;
try
{
// Performs the CreateFile call above resulting in an IntPtr handle
hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE);
success = NativeMethods.HidD_GetFeature(hidHandle, ref buffer, buffer.Length);
// at this point, success is false, and buffer has gone from 9 bytes to 1
if(success)
{
Array.Copy(buffer, 0, data, 0, Math.Min(data.Length, _deviceCapabilities.FeatureReportByteLength));
}
else
{
//Yes, i know casting to a byte isn't good here; it's dirty but helping me debug for now
data[0] = (byte)Marshal.GetLastWin32Error(); //returns 23 (verified, actual) - ERROR_CRC
}
}
catch (Exception exception)
{
throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception);
}
finally
{
if (hidHandle != IntPtr.Zero)
CloseDeviceIO(hidHandle);
}
return success;
}
I've read there are some issues with working with system devices like keyboards and mice, and that may or may not play in here, however I know that it's possible to interact with the device in the way I'm trying because I was able to do so with SimpleHidWrite. That said, I haven't ruled anything out and any ideas are welcome.
Let me know if I need to provide any more info.
I think this declaration may be the problem.
[DllImport("hid.dll", SetLastError = true)]
static internal extern bool HidD_GetFeature(IntPtr hidDeviceObject, ref byte[] lpReportBuffer, int reportBufferLength);
Try changing the lpReportBuffer ref parameter to out, or omitting it altogether.
Yes your declaration is the problem.
use:
[DllImport("hid.dll", SetLastError = true)]
protected static extern bool HidD_GetFeature( IntPtr hDevInfo,
Byte[] lpReportBuffer,
Int32 ReportBufferLength);
NO: ref or out
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\
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.