I made a program which prints on a specific printer, but I have multiple printers to setup, so I thought that I will make the site from where they print, do it for me. I have tried to do it via. powershell etc., and it works, just not in my .net core api, but it works in my .net core console. I have given the IUSR rights to manage printers, ports etc. It can make the printer port, but not create the printer. When I used powershell, I got that "Add-Printer" didn't exist, and I tried putting it in powershell myself, and it worked perfectly.. Here is my code:
class Program
{
static void Main(string[] args)
{
IntPtr mystrptr = new IntPtr(0);
bool mysend;
IntPtr mysend2;
PRINTER_INFO_2 pi = new PRINTER_INFO_2();
string printer = "05_Vognmodtagelse_Label";
pi.pPrinterName = printer;
pi.pPortName = printer;
pi.pDriverName = "Brother PT-P950NW";
pi.pDevMode = mystrptr;
pi.pPrintProcessor = "WinPrint";
pi.pDatatype = "RAW";
pi.pSecurityDescriptor = mystrptr;
mysend2 = AddPrinter(null, 2, ref pi);
}
[DllImport("winspool.drv", CharSet = CharSet.Auto)]
static extern IntPtr AddPrinter(string pName, uint Level, [In] ref PRINTER_INFO_2 pPrinter);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct PRINTER_INFO_2
{
public string pServerName,
pPrinterName,
pShareName,
pPortName,
pDriverName,
pComment,
pLocation;
public IntPtr pDevMode;
public string pSepFile,
pPrintProcessor,
pDatatype,
pParameters;
public IntPtr pSecurityDescriptor;
public uint Attributes,
Priority,
DefaultPriority,
StartTime,
UntilTime,
Status,
cJobs,
AveragePPM;
}
}
Note: It is the code from the console app.
Related
I would like to allow the user to connect to paired audio devices directly from the app instead of navigating to the bluetooth settings manually.
I am successfully listing all bluetooth devices using WinRT Apis:
var result = await DeviceInformation.FindAllAsync(BluetoothDevice.GetDeviceSelector());
// allow user to select a device
DeviceInfo d = await SelectDevice(result);
BluetoothDevice bluetoothDevice = await BluetoothDevice.FromIdAsync(d.Id);
As the WinRT-Apis do not expose any "Connect" interface (Remember, I want to connect the device as Windows would not communicate with it myself), I'm exploring using P/Invoke, so I use the following after reading this answer on superuser.com which suggests using BluetoothSetServiceState:
// Definitions
private const string bluetoothDll = "bthprops.cpl";
[DllImport(bluetoothDll, ExactSpelling = true, SetLastError = true)]
private static extern uint BluetoothSetServiceState(IntPtr hRadio, ref BLUETOOTH_DEVICE_INFO pbtdi, ref Guid pGuidService, uint dwServiceFlags);
[DllImport(bluetoothDll, SetLastError = true)]
private static extern IntPtr BluetoothFindFirstRadio(ref Bluetooth_Find_Radio_Params pbtfrp, out IntPtr phRadio);
[DllImport(bluetoothDll, SetLastError = true)]
private static extern bool BluetoothFindRadioClose(IntPtr findHandle);
[DllImport(bluetoothDll, SetLastError = true)]
private static extern uint BluetoothGetDeviceInfo(IntPtr hRadio, ref BLUETOOTH_DEVICE_INFO pbtdi);
private const uint BLUETOOTH_SERVICE_DISABLE = 0;
private const uint BLUETOOTH_SERVICE_ENABLE = 0x00000001;
// Code (using the bluetoothDevice obtained from the WinRT Api)
using(var pointer = GetRadioPointer())
{
BLUETOOTH_DEVICE_INFO deviceInfo = new BLUETOOTH_DEVICE_INFO
{
Address = bluetoothDevice.BluetoothAddress,
dwSize = (uint)Marshal.SizeOf<BLUETOOTH_DEVICE_INFO>()
};
uint result = BluetoothGetDeviceInfo(pointer.Handle, ref deviceInfo);
Guid serviceRef = InTheHand.Net.Bluetooth.BluetoothService.Handsfree;
result = BluetoothSetServiceState(pointer.Handle, ref deviceInfo, ref serviceRef, 1);
}
// I get the radio like this:
private RadioHandle GetRadioPointer()
{
Bluetooth_Find_Radio_Params pbtfrp = new Bluetooth_Find_Radio_Params();
pbtfrp.Initialize();
IntPtr findHandle = IntPtr.Zero;
try
{
findHandle = BluetoothFindFirstRadio(ref pbtfrp, out IntPtr phRadio);
return new RadioHandle(phRadio);
}
finally
{
if (findHandle != IntPtr.Zero)
{
BluetoothFindRadioClose(findHandle);
}
}
}
However, I cannot get it to work. BluetoothSetServiceState always returns 87, which is ERROR_INVALID_PARAMETER and nothing happens. Any idea on how to solve this? Using the command line tools referenced in the superuser-post, it works...
Thanks for your help.
I found it out by chance myself and it works now. Depending whether the service is already in the state (even if the device is disconnected), you will need to turn it off before. So turning it off and on again works:
BluetoothSetServiceState(pointer.Handle, ref deviceInfo, ref serviceRef, 0);
BluetoothSetServiceState(pointer.Handle, ref deviceInfo, ref serviceRef, 1);
So as it turns out, you can connect a device if you enumerate the services, turn all off and on again. Disconnecting works by turning all off. When the last one is off, Windows disconnects the device.
Why would you ever expect that the following line would work:
private const string bluetoothDll = "bthprops.cpl";
when MSDN's page states:
DLL: Bthprops.dll
?
I am trying to get my C# script to output into ets2 so that it will drive for me (wasd). For testing I am using the space bar. I have tested the code in chrome and notepad, where it works and puts down a space. Would anyone know what is going wrong?
Update:
I wrote a little bit of test code for python using the keyboard module and I got it to work. Would it be possible to make "space" into a variable that I could change from C#?
Python Code:
import keyboard, time
time.sleep(5)
keyboard.press_and_release("space")
The Threads and Windows in Spy++:
I use the following code:
public const int WM_KEYDOWN = 0x0100;
const int VK_SPACE = 0x20;
static void Main(string[] args)
{
System.Threading.Thread.Sleep(2000); // gives user time to switch tabs
IntPtr programloc = WindowHelper.GetForegroundWindow();
// I also tried using (from Spy++) FindWindow("Euro Truck Simulator 2", "prism3d");
if (programloc == IntPtr.Zero) throw new SystemException();
WindowHelper.PostMessage(programloc, WM_KEYDOWN, VK_SPACE, 0);
}
and the following module WindowHelper (combination of multiple Stackoverflow and docs.microsoft pages):
class WindowHelper
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern IntPtr FindWindow(
string lpClassName,
string lpWindowName);
[System.Runtime.InteropServices.DllImport("User32.dll")]
public static extern IntPtr FindWindowEx(
IntPtr hwndParent,
IntPtr hwndChildAfter,
string lpszClass,
string lpszWindos);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "GetForegroundWindow")]
public static extern IntPtr GetForegroundWindow();
}
Your code is exiting immediately. Is this intentional? Make a call to Console.Readline() at the end of main to block until you give the console input.
I have been able to find a (temporary) fix myself. This fix is however not very performance friendly. Anyone having better suggestions is welcome.
Using the keyboard function I described in my question, I made a python script which uses arguments. This python script is started by the C# script using the following code:
static public string run_python(string cmd, string args)
{
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"C:\Users\Stijn\AppData\Local\Programs\Python\Python36\python.exe";
start.Arguments = string.Format("\"{0}\" \"{1}\"", cmd,args);
start.UseShellExecute = false;// Do not use OS shell
start.CreateNoWindow = true; // We don't need new window
start.RedirectStandardOutput = true;// Any output, generated by application will be redirected back
start.RedirectStandardError = true; // Any error in standard output will be redirected back (for example exceptions)
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string stderr = process.StandardError.ReadToEnd(); // Here are the exceptions from our Python script
string result = reader.ReadToEnd(); // Here is the result of StdOut(for example: print "test")
return result + stderr;
}
}
}
The python code consists of:
from keyboard import press_and_release
from sys import argv as args
for i in range(len(args)-1):
press_and_release(args[i+1])
Using the keyboard module as can be found at https://pypi.org/project/keyboard/
We have an IIS WCF service that launches another process (app.exe) as a different user. I have complete control over both applications (and this is a dev environment for now). The IIS app pool runs as me, a domain user (DOMAIN\nirvin), who is also a local administrator on the box. The second process is supposed to run as a local user (svc-low). I am using System.Diagnostics.Process.Start(ProcessStartInfo) to launch the process. The process launches successfully - I know because there are no exceptions thrown, and I get a process ID. But the process dies immediately, and I get an error in the Event Log that looks like:
Faulting application name: app.exe, version: 1.0.3.0, time stamp: 0x514cd763
Faulting module name: KERNELBASE.dll, version: 6.2.9200.16451, time stamp: 0x50988aa6
Exception code: 0xc06d007e
Fault offset: 0x000000000003811c
Faulting process id: 0x10a4
Faulting application start time: 0x01ce274b3c83d62d
Faulting application path: C:\Program Files\company\app\app.exe
Faulting module path: C:\Windows\system32\KERNELBASE.dll
Report Id: 7a45cd1c-933e-11e2-93f8-005056b316dd
Faulting package full name:
Faulting package-relative application ID:
I've got pretty thorough logging in app.exe (now), so I don't think it's throwing errors in the .NET code (anymore).
Here's the real obnoxious part: I figured I was just launching the process wrong, so I copied my Process.Start() call in a dumb WinForms app and ran it on the machine as myself, hoping to tinker around till I got the parameters right. So of course that worked the very first time and every time since: I'm able to consistently launch the second process and have it run as intended. It's only launching from IIS that doesn't work.
I've tried giving svc-low permission to "Log on as a batch job" and I've tried giving myself permission to "Replace a process level token" (in Local Security Policy), but neither seem to have made any difference.
Help!
Environment Details
Windows Server 2012
.NET 4.5 (all applications mentioned)
Additional Details
At first app.exe was a Console Application. Trying to launch was making conhost.exe generate errors in the Event Log, so I switched app.exe to be a Windows Application. That took conhost out of the equation but left me the situation described here. (Guided down that path by this question.)
The ProcessStartInfo object I use looks like this:
new ProcessStartInfo
{
FileName = fileName,
Arguments = allArguments,
Domain = domainName,
UserName = userName,
Password = securePassword,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = false
//LoadUserProfile = true //I've done it with and without this set
};
An existing question says I should go down to the native API, but a) that question addresses a different situation and b) the success of the dumb WinForms app suggests that Process.Start is a viable choice for the job.
I ended up opening a case with Microsoft, and this is the information I was given:
Process.Start internally calls CreateProcessWithLogonW(CPLW) when credentials are specified. CreateProcessWithLogonW cannot be called from a Windows Service Environment (such as an IIS WCF service). It can only be called from an Interactive Process (an application launched by a user who logged on via CTRL-ALT-DELETE).
(that's verbatim from the support engineer; emphasis mine)
They recommended I use CreateProcessAsUser instead. They gave me some useful sample code, which I then adapted to my needs, and now everything works great!
The end result was this:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
public class ProcessHelper
{
static ProcessHelper()
{
UserToken = IntPtr.Zero;
}
private static IntPtr UserToken { get; set; }
public int StartProcess(ProcessStartInfo processStartInfo)
{
LogInOtherUser(processStartInfo);
Native.STARTUPINFO startUpInfo = new Native.STARTUPINFO();
startUpInfo.cb = Marshal.SizeOf(startUpInfo);
startUpInfo.lpDesktop = string.Empty;
Native.PROCESS_INFORMATION processInfo = new Native.PROCESS_INFORMATION();
bool processStarted = Native.CreateProcessAsUser(UserToken, processStartInfo.FileName, processStartInfo.Arguments,
IntPtr.Zero, IntPtr.Zero, true, 0, IntPtr.Zero, null,
ref startUpInfo, out processInfo);
if (!processStarted)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
uint processId = processInfo.dwProcessId;
Native.CloseHandle(processInfo.hProcess);
Native.CloseHandle(processInfo.hThread);
return (int) processId;
}
private static void LogInOtherUser(ProcessStartInfo processStartInfo)
{
if (UserToken == IntPtr.Zero)
{
IntPtr tempUserToken = IntPtr.Zero;
string password = SecureStringToString(processStartInfo.Password);
bool loginResult = Native.LogonUser(processStartInfo.UserName, processStartInfo.Domain, password,
Native.LOGON32_LOGON_BATCH, Native.LOGON32_PROVIDER_DEFAULT,
ref tempUserToken);
if (loginResult)
{
UserToken = tempUserToken;
}
else
{
Native.CloseHandle(tempUserToken);
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
private static String SecureStringToString(SecureString value)
{
IntPtr stringPointer = Marshal.SecureStringToBSTR(value);
try
{
return Marshal.PtrToStringBSTR(stringPointer);
}
finally
{
Marshal.FreeBSTR(stringPointer);
}
}
public static void ReleaseUserToken()
{
Native.CloseHandle(UserToken);
}
}
internal class Native
{
internal const int LOGON32_LOGON_INTERACTIVE = 2;
internal const int LOGON32_LOGON_BATCH = 4;
internal const int LOGON32_PROVIDER_DEFAULT = 0;
[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;
[MarshalAs(UnmanagedType.LPStr)]
public string lpReserved;
[MarshalAs(UnmanagedType.LPStr)]
public string lpDesktop;
[MarshalAs(UnmanagedType.LPStr)]
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public System.UInt32 nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("advapi32.dll", EntryPoint = "LogonUserW", SetLastError = true, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
internal extern static bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUserA", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
internal extern static bool CreateProcessAsUser(IntPtr hToken, [MarshalAs(UnmanagedType.LPStr)] string lpApplicationName,
[MarshalAs(UnmanagedType.LPStr)] string lpCommandLine, IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes, bool bInheritHandle, uint dwCreationFlags, IntPtr lpEnvironment,
[MarshalAs(UnmanagedType.LPStr)] string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
internal extern static bool CloseHandle(IntPtr handle);
}
There are some pre-requisites to making this code work. The user running it must have the user right to 'Replace a process level token' and 'Adjust memory quotas for a process', while the 'other user' must have the user right to 'Log on as a batch job'. These settings can be found under the Local Security Policy (or possibly through Group Policy). If you change them, a restart will be required.
UserToken is a property that can be closed via ReleaseUserToken because we will call StartProcess repeatedly and we were told not to log the other user on again and again.
That SecureStringToString() method was taken from this question. Using SecureString was not part of Microsoft's recommendation; I did it this way so as not to break compatibility with some other code.
Exception code: 0xc06d007e
This is an exception that's specific to Microsoft Visual C++, facility code 0x6d. The error code is 0x007e (126), ERROR_MOD_NOT_FOUND, "The specified module could not be found". This exception is raised when a delay-loaded DLL cannot be found. Most programmers have the code that generates this exception on their machine, vc/include/delayhlp.cpp in the Visual Studio install directory.
Well, it is the typical "file not found" mishap, specific to a DLL. If you have no idea what DLL is missing then you can use SysInternals' ProcMon utility. You'll see the program search for the DLL and not finding just before it bombs.
A classic way to get poorly designed programs to crash with Process.Start() is by not setting the ProcessStartInfo.WorkingDirectory property to the directory in which the EXE is stored. It usually is by accident but won't be when you use the Process class. Doesn't look like you do so tackle that first.
According to undocprint given a job ID it should be possible to retrieve the spool file for the job using OpenPrinter and ReadPrinter by opening the printer using a string with format "PrinterName,Job xxxx". The MSDN documentation lists this method as well, though with an additional space after the comma "PrinterName, Job xxxx".
Whenever I try to call this method from my test application (using either string format) I get ERROR_ACCESS_DENIED (Windows 8 x64). Why is this and what do I need to do to get this working?
I'm running the test app as admin and have no trouble pausing jobs or printers or accessing other information.
I know the ID I'm using is valid because for an invalid ID it returns ERROR_INVALID_PRINTER_NAME instead.
The code I'm using:
public static void OpenPrinter(String printerName,
ref IntPtr printerHandle,
ref PRINTER_DEFAULTS defaults) {
if (OpenPrinter(printerName, ref printerHandle, ref defaults) == 0) {
throw new Win32Exception(Marshal.GetLastWin32Error(),
string.Format("Error getting access to printer: {0}", printerName));
}
}
[DllImport("winspool.drv", EntryPoint = "OpenPrinterW", SetLastError = true, CharSet = CharSet.Unicode,
ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern int OpenPrinter(String pPrinterName, ref IntPtr phPrinter, ref PRINTER_DEFAULTS pDefault);
[StructLayout(LayoutKind.Sequential)]
public struct PRINTER_DEFAULTS {
public IntPtr pDatatype;
public IntPtr pDevMode;
public uint DesiredAccess;
}
Turns out that pDefaults must be passed NULL and then everything works.
This requires changing the extern definition to take an IntPtr or similar.
I haven't seen any documentation about why this might be (in fact the MSDN docs state that this passing NULL should be the same as requesting USE access), but it definitely fixes the issue in our testing.
Permissions. Are you running with administrator rights?
I have a windows service and tried to map it using the pinvoke. The code works fine in a console application, but it has no effect in a windows service. According to this:
"net use" command in a Windows Service
I should drop the net use approach and instead give the appropriate privileges, so that my service can access the UNC path ( \server\share\file-path ). How do I set up these privileges? I assume it's basic Windows privileges that should be set somewhere. Any help is appreciated.
[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern System.UInt32 NetUseAdd(string UncServerName, int Level, ref USE_INFO_2 Buf, out uint ParmError);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct USE_INFO_2
{
internal LPWSTR ui2_local;
internal LPWSTR ui2_remote;
internal LPWSTR ui2_password;
internal DWORD ui2_status;
internal DWORD ui2_asg_type;
internal DWORD ui2_refcount;
internal DWORD ui2_usecount;
internal LPWSTR ui2_username;
internal LPWSTR ui2_domainname;
}
and...
static void Main()
{
USE_INFO_2 useInfo = new USE_INFO_2();
useInfo.ui2_remote = #"\\xx.xx.xx.xx\E$"; // "\\xx.xx.xx.xx\E$"
useInfo.ui2_password = "********";
useInfo.ui2_asg_type = 0; //disk drive
useInfo.ui2_usecount = 1;
useInfo.ui2_username = "Admin";
useInfo.ui2_domainname = "rendering";
uint paramErrorIndex;
uint returnCode = NetUseAdd(String.Empty, 2, ref useInfo, out paramErrorIndex);
if (returnCode != 0)
{
throw new Win32Exception((int)returnCode);
}
...
You could launch your Windows service as a domain/windows user that does have access to this path.