When are AD services ready? - c#

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?

Related

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

WinSpool OpenPrinter Access Denied

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));
}
}

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.

How do you pass user credentials from one process to another for Impersonation in .NET 1.1?

I have a Windows Service (written in .NET 1.1) running under a specific user account and instances of the service running on several servers.
I would like to pass user credentials (username, password, domain) to the service from a WinForms application and have the service read/write files in the server's local file system impersonating the passed-in credentials.
Is it better to pass the username, domain, and password and have the Windows Service perform the Impersonation? I don't see how to serialize a WindowsIdentity and pass one as a parameter to have the service then perform the Impersonate() and Undo() around the I/O.
As a container object, System.Net.NetworkCredential is not marked serializable so passing the three individual parameters seems logical. I'm essentially using the Impersonation routine found in KB306158.
The short answer is: don't pass the credentials at all. That approach is insecure.
Instead you should be looking to leverage the secure mechanism provided by the OS for achieving what you are wanting to do. It is called SSPI (Security Support Provider Interface). Using this the processes exchange a series of tokens generated by an OS-level security provider, to set up a security context without the need for passing credentials in user mode code.
If you were able to upgrade your service to use .NET 3.5, you could use WCF to do the IPC, and, appropriately configured, it would take care of the details of the SSPI handshake, and enable impersonation straightforwardly.
If you are stuck with .NET 1.1, then take a look at the articles and sample code provided here and here, which show how to invoke SSPI from managed code and use it to secure a .NET remoting channel.
I dont know how directly this correlates to your needs but this is a snippet of the impersonation code I used in an application that accesses the registry and file system of remote machines given valid credentials. The LogonUser method takes username password and servername as args which you could pass via your winform app.
edit You will have to set up a form of inter-process communication between your winform app and the services running on the separate computers. My apologies I thought this was a question about how to impersonate not how to send information to your process. As far as methods for IPC go there are quite a few options. Take a look at this site, it will provide far more information than I can. Your best bet is going to be using named pipes.
[DllImport("advapi32.dll",EntryPoint = "LogonUser", SetLastError = true)]
public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
IntPtr admin_token = IntPtr.Zero;
WindowsIdentity wid = WindowsIdentity.GetCurrent();
WindowsIdentity wid_admin;
WindowsImpersonationContext wic;
if (LogonUser(user, servername, pass, 9, 0, ref admin_token))
{
wid_admin = new WindowsIdentity(admin_token);
wic = wid_admin.Impersonate();
//do stuff with new creds here
}
I don't think you can pass the Network Credential object directly to another process, it's based on an underlying windows api and I'm guessing there would be all kinds of bad juju involved in letting processes pass around their auth tokens.
I would take the approach you mentioned, if possible, and pass the log on credentials (user/pass) to the service and let it use those for the impersonation.

Categories

Resources