WinSpool OpenPrinter Access Denied - c#

This is 335th time this question being asked, by I've found no answer.
I'm trying to send raw data directly to printer via WinSpool api from ASP.net C# application.
My code is just a copy from here.
Error goes here
if( OpenPrinter( szPrinterName.Normalize(), out hPrinter, IntPtr.Zero ) )
It works fine for local printer but for shared network printer the result of OpenPrinter (result of GetLastError actually) is always 5 - Access Denied.
I've tried
different values for PRINTER_DEFAULTS with different combinations of DesiredAccess
give administrator privileges to user
setup printer like this
I must note that I just want to print, not change printer config or something that require administrative rights.
I can print to this shared printer from server using Printer Option page and test tool embedded in it. So printer works. How to gain access to it via API?
Update: looks like this code is working fine if called from Windows Application or Console Application. Then why access denied in Web Application?
Update 2: problem may be caused by the fact that printer installed on host PC and shared with virtual PC (or in production: printer is installed inside domain and shared to PC in DMZ) and there is no proper way to grant rights to this printer to users of virtual PC (or users outside of domain)
Update 3: and here is one more fact. If I browse to host PC from virtual in explorer (like this \\host_pc\C$) I get notified to enter user name and password to access host PC. If I check "save password" after that the whole "access denied"-problem will go away until I change password on host PC.

By default, your ASP.Net website running under IIS runs under a low-privilege local user account IIS_APPPOOL\mysite. In order for you to allow clients to access domain resources from the website, you'll need to change the user that IIS runs under (known as the application pool identity) to a domain user that has the correct rights to everything (both the network printer, and IIS itself).
The simplest solution (there may be more secure ones) is to change the IIS APPPool to use the built-in NetworkService account. This account is automatically added to your domain as MyDomain\MyHostName$, so you can use that to grant printer permissions (or whatever else is needed).
To change the app pool identity, just open the IIS manager, select the right application pool and then hit "Advanced Settings", and look for the setting "Identity".
More info here:
http://www.iis.net/learn/manage/configuring-security/application-pool-identities

Adding my answer since the accepted answer didn't solve my issue, to any who may get this error this might be useful to you if:
You get an error building for AnyCPU or x64.
You don't get an error building for x86.
The issue seems to be in GC recycling the PRINTER_DEFAULTS struct and than the OpenPrinter tries writing to that location. The suggested solution is to use a class which will stay on the stack.
public class PrinterSettings
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal class PRINTERDEFAULTSClass
{
public IntPtr pDatatype;
public IntPtr pDevMode;
public int DesiredAccess;
}
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, PRINTERDEFAULTSClass pdc);
public DEVMODE GetPrinterSettings(string PrinterName)
{
DEVMODE dm;
var pdc = new PRINTERDEFAULTSClass
{
pDatatype = new IntPtr(0),
pDevMode = new IntPtr(0),
DesiredAccess = PRINTER_ALL_ACCESS
};
var nRet = Convert.ToInt32(OpenPrinter(PrinterName,
out hPrinter, pdc));
}
}

Related

When are AD services ready?

I have a Windows service running as LocalSystem that depends on Active Directory. After a computer restart, the AD name isn't always immediately available on the server where the service is running when the service starts. For instance, this throws an exception:
var ADName = System.DirectoryServices.ActiveDirectory.Domain.GetComputerDomain().Name
If I make the call 30 seconds later, it does not throw an exception but correctly returns the AD domain name.
If at the same time the exception is thrown I use other APIs, they succeed (for instance):
System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName
Or use interop:
[DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int NetGetJoinInformation(string server, out IntPtr domain, out NetJoinStatus status);
The NetGetJoinInformation() gives the NetBIOS domain name, but not the full. But, it at least succeeds without error.
Currently I configure the service to only be dependent on the following services: Netlogon. At one point I remember trying others, but had some difficulty, so I wanted to wait until at least Netlogon was up and running.
Are there others I should include so that the call to get the AD domain name succeeds without throwing an exception?

.NET c# service running as LocalSystem alters ProxyServer in registry, but browser's won't notice until (re)start

I have a .NET c# service running as LocalSystem on Windows10 64bit. It's job is to alter the "ProxyEnable" and "ProxyServer" values located under
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings
in the registry. It does so, for all "human" Users of the System (that is, the key starting with "S-1-5-21.."). It works fine and all browsers the user starts up use this configuration (proxy or no proxy).
However, browsers that are still running won't notice changes (turning the proxy setting on or off) until a long time - OR until i manually just open and close the Windows "Internet Options > Proxy Settings" (without doing changes or saving them) or restart the browser(s). The latter two make the System re-read and propagate the settings to all (other) open browsers.
Now DllImport'ing wininet.dll and using the InternetSetOption does work
[DllImport("wininet.dll", SetLastError = true)]
private static extern bool InternetSetOption(
IntPtr hInternet,
int dwOption,
IntPtr lpBuffer,
int lpdwBufferLength);
private const int INTERNET_OPTION_PROXY_SETTINGS_CHANGED = 95;
private const int INTERNET_OPTION_SETTINGS_CHANGED = 39;
private const int INTERNET_OPTION_REFRESH = 37;
...
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_PROXY_SETTINGS_CHANGED, IntPtr.Zero, 0);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
but only when i perform this piece of code as the logged in user. When the service performs it, it doesn't do anything. I guess it's because as LocalSystem the service is applying it to the wrong scope.
I have tinkered around a lot. Trying to start another process as the user is no option as this would require credentials. Also the service using LocalSystem is set.
I am just convinced that as LocalSystem, that is close to Administrator in terms of privileges, there must be a way to tell all applications and browsers, that the Proxy Settings have changed. Its just the propagation like opening and closing the Proxy Settings-GUI invokes, that i need to trigger somehow. But i don't know how. Do you?
This is how environment variables & Reg keys function in windows - as I understand it all new processes get a snapshot of all variables when they start, but thats it. They will only update when you restart the process.
See C# : How to change windows registry and take effect immediately

How to force Windows to create the running user's profile directory

I am having an issue with a process being run, where the profile directory of the process' user has not yet been created.
To explain, here are the details of how this is happening:
We run a large distributed server grid, and are using (parts of) DataSynapse to execute processes on this grid. For those familiar with DataSynapse, the Engine is configured to run the particular service with "RunAs", where we use a certain AD domain service account for the service processes. I believe the problem is that DataSynapse, when running the process under "runas", does not set the LoadUserProfile flag (nor should it). Whatever the precise reason, if the "runas" service account (and AD domain account) has never logged on to some of the grid machines, then those machines will not have the user profile directory for the account.
For those not familiar with DataSynapse, here is a more generic explanation. On each machine on the grid, there is a process running, I'll call it dsService, and it runs under the credentials of the local machine's system account (or some similar account with elevated credentials). The process dsService will spawn a child process, say childProcess, but it runs childProcess under the credentials of our AD domain account, which I'll call serviceUser. There are thousands of machines on the grid, and typically they are never logged on to manually. In particular, the profile directory C:\users\serviceUser may not initially exist. Once it is created once, there are not further issues. But if new nodes are added to the grid, typically they will not have the C:\users\serviceUser initially. The problem is that when the dsService spawns childProcess, C:\users\serviceUser does not get created, and we need it.
I believe that this is because dsService does not set the LoadUserProfile flag to true when spawning childProcess, though I am not certain.
In any event, childProcess is a (.net) process (running as serviceUser) under our control, and I would like to know if there is a way (in C#) that childProcess can force the OS to create the running user's profile directory C:\users\serviceUser when it determines that it does not yet exist?
Edit:
Experimentation has confirmed that if one starts a process under another user ID, and the user's profile directory is not there (more specifically, if the user's local profile has not been created yet - merely deleting a pre-existing profile directory creates a different situation, one we're not interested in anyway), then (1) the profile directory (and one presumes, the local profile) gets created if the process is started with the LoadUserProfile set to true; and (2) the profile directory (and one presumes, the local profile) does NOT get created if the process is started with the LoadUserProfile set to false. This makes sense, and is as one would expect.
Related post: stackoverflow.com/q/9008742/1082063
If the running account has admin privileges, the following code will cause the creation of the running account's profile, including its UserProfile directory. Without admin, I don't know if it is possible:
using System.Runtime.InteropServices;
...
[DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int CreateProfile(
[In] string pszUserSid,
[In] string pszUserName,
System.Text.StringBuilder pszProfilePath,
int cchProfilePath);
....
public static string getUserProfilePath()
{
string userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if(string.IsNullOrWhiteSpace(userProfilePath) || !Directory.Exists(userProfilePath))
{ //This will only work if we have admin...
var pathBuf = new System.Text.StringBuilder(240);
var Up = System.DirectoryServices.AccountManagement.UserPrincipal.Current;
if( 0 == CreateProfile(Up.Sid.ToString(), Up.SamAccountName, pathBuf, pathBuf.Capacity) }
{
userProfilePath = pathBuf.ToString();
}
}
return userProfilePath;
}
If anyone can tell me how to do this when the account is not admin, they will get their answer accepted as the correct answer. Until then, this at least gives others some idea.

Reboot machine from C#/Forms App while session is locked

Referring to this question: .net - Reboot machine from a C#/WPF App
I am attempting to create a c#/.net app that can restart the machine even if the session is locked (i.e., user is logged in, this app is running, but session is locked).
I tried this from the question: System.Diagnostics.Process.Start("shutdown.exe", "-r -t 0");
but apparently that only works if the session is unlocked. Additionally, after reading this: MSDN - InitiateSystemShutdown Function it seems the InitiateSystemShutdown function will display the System Shutdown dialog box, which doesn't seem like it will suite my purposes.
Are there any other methods of doing this?
The ExitWindowsEx function accomplished what I was trying to do.
Using:
[DllImport("user32.dll", SetLastError = true)]
public static extern int ExitWindowsEx(ExitWindows uFlags, ShutdownReason dwReason);
after adjusting token privileges, and using uFlags 0x06 (reboot / force). I used dwReason 0 as well. This function will restart the machine whether or not the session is locked.
here

Stopping a windows service via program

Programmatically stopping a windows service in c# generates below listed System.InvalidOperationException
{Access is denied}
If i start/stop through windows interface then everything works fine!
I'm an Admin user and running the service under Windows 7
I'm not sure how you are trying to stop it, but I tried this on my system now, and this approach at least works fine:
var p = Process.Start(new ProcessStartInfo
{
FileName = "net",
Arguments = "stop NameOfService",
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
});
p.WaitForExit(); //add this line if you want to make sure that the service is stopped before your program execution continues
This sounds like a UAC issue - try running the application which shall control the service as admin (right-click, "Run as Administrator").
Note that even an administrator account does not have full privileges unless you explicitly run applications in administrator mode - this was introduced for protecting the user from malicious software in Windows Vista and has been there since then.
If authentication is the problem ...
You can programatically control the user from which you're doing stuff with, using
WindowsIdentity
and
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(...)
I don't know if that would work, but it could be worth a shot.
IntPtr token IntPtr.Zero;
LogonUser(username, password ..., ref token) //and some other parameters
var identity = WindowsIdentity(token);
using(identity.Impersonate())
{
//do stuff here using another identity
//find service and stop it
}
edit: This can be used for authentication on remote servers.

Categories

Resources