C# cannot programmatically get a user domain given the SID - c#

I'm trying to extract all the users that have a security rule to a certain folder, to do that I'm using a C# application, however I'm having trouble getting the correct domain for the user.
Let's say that I'm on domain CURRENT, but some users that have access to the folder are also on the domain OLD. Between the domain there's a trust. However some users that have been migrated from OLD to CURRENT for some reason appear to be on CURRENT when I lookup their SID. The strange thing however is that windows explorer correctly shows them to be on domain OLD.
The code
public IdentityReference GetUser(string SID)
{
return ( new NTAccount(SID) ).Translate(typeof(NTAccount));
}
As you can see, it gets the SID of the user and returns the NTAccount (DOMAIN\user). To get the SID I use the following code:
DirectorySecurity ACL = Directory.GetAccessControl(folder);
AuthorizationRuleCollection rules = ACL.GetAccessRules(true, true, typeof(SecurityIdentifier));
foreach (FileSystemAccessRule rule in rules)
{
IdentityReference user = GetUser(rule.IdentityReference.value);
//do stuff ...
}
I also have a PowerShell script that does the same thing:
echo (New-Object System.Security.Principal.SecurityIdentifier($sid)).Translate([System.Security.Principal.NTAccount]).Value
The problem
Now, I have user OLD\user1, that is, however, also present in the new domain as CURRENT\user1. When I right click on windows explorer it correctly shows me the user as OLD\user1, but when I run the C# code it always gives me CURRENT\user1, which is not what I want.
Strangely enough, when I try to do the same thing in PowerShell, the user is also correctly identified as OLD\user1. Even more strangely, if I run the PowerShell script and only then I run the C# code I get the correct result, however after exactly 10 minutes the C# code is wrong again.
What I have already tried
Given these information, I figured out that it must be a sort of caching problem (as it also seem to happen to other people) and also that for some reason C# and PowerShell handle the lookup request in a different way,(See EDIT1 down below) so I've tried the following:
Running PowerShell code from inside C#
PowerShell ps = PowerShell.Create();
ps.AddScript("New-Object System.Security.Principal.SecurityIdentifier(\""+SID+"\")).Translate([System.Security.Principal.NTAccount]");
return (IdentityReference)(ps.Invoke()[0].BaseObject);
Running the same script saved on the same folder as the executable from within the exe
Process p = new Process();
p.StartInfo.FileName = #"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe";
p.StartInfo.Arguments = String.Format("-File \"{0}\" {1}", Directory.GetCurrentDirectory() + #"\get_user.ps1", SID);
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
p.WaitForExit();
return (new NTAccount(output)).Translate(typeof(NTAccount));
Using P/Invoke to do the lookup with the WIN32API
enum SID_NAME_USE
{
SidTypeUser = 1,
SidTypeGroup,
SidTypeDomain,
SidTypeAlias,
SidTypeWellKnownGroup,
SidTypeDeletedAccount,
SidTypeInvalid,
SidTypeUnknown,
SidTypeComputer
}
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool LookupAccountSid(
string lpSystemName,
[MarshalAs(UnmanagedType.LPArray)] byte[] Sid,
StringBuilder lpName,
ref uint cchName,
StringBuilder ReferencedDomainName,
ref uint cchReferencedDomainName,
out SID_NAME_USE peUse);
byte[] sid = { /* the SID in bytes */ };
StringBuilder name = new StringBuilder();
uint cchName = (uint)name.Capacity;
StringBuilder referencedDomainName = new StringBuilder();
uint cchReferencedDomainName = (uint)referencedDomainName.Capacity;
SID_NAME_USE sid_use;
LookupAccountSid(null, sid, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sid_use);
if (Marshal.GetLastWin32Error() == 0)
Console.WriteLine(String.Format("Account({0}): {2}\\{3}", sid_use, "...", referencedDomainName.ToString(), name.ToString()));
But none of these solved the issue. Please also note that changing the server caching option is not really a solution, as I'm not able to do that.
EDIT1:
Apparently, it's not correct that PowerShell handles the request in a different way, what I forgot to mention is that in the PS code, before requesting the NTAccount from the SID, I did the reverse: I first requested the SID from the NTAccount, like this:
echo (New-Object System.Security.Principal.NTAccount("OLD\user1")).Translate([System.Security.Principal.SecurityIdentifier]).Value
echo (New-Object System.Security.Principal.SecurityIdentifier($sid)).Translate([System.Security.Principal.NTAccount]).Value
And this must have triggered the cache, so it most definitely is a caching problem.
EDIT2
I've managed to "solve" the issue by moving the executable to a computer under the OLD domain (as this is where the place where it should have carried out its task remotely from another machine), but I will leave the question unsolved as it is not really the best solution.

Related

Access Violation Error only when running user service account

In our existing application we are using a windows service which has a DLL import to call some functions in native assembly. This has been working fine as long as the service where this native function is called is running as Network Service. It also works when the service runs as LocalSystem.
In order to set customized privileges , we have decided to add support for using a Service account. However simply running the service as a newly created windows user account results in Access Violation error during the Native method call.
Similarly, I tried with a domain account which has administrator permissions on the system to run the service, but still it results in the same problem.
I cannot find any documentation on MSDN where specific permission for user account are listed for DLL Import to work. Can anyone tell me what I might be doing wrong ?
C# Code
[DllImport("lmgr11.dll", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern string GetFeaturelist(short jobType, int flag);
C Code
char* WINAPI GetFeaturelist(int type, int flag)
{
char * featureList;
char ** features, ** tempFeatureList;
int counter = 0;
if (!lm_job)
{
return NULL;
}
features = tempFeatureList = lc_feat_list(lm_job,flag,NULL);
//loop to know the number of features
while(*tempFeatureList != NULL)
{
counter++;
tempFeatureList++;
}
featureList = (char*) CoTaskMemAlloc(counter * 50);
strcpy(featureList,"");
while(*features != NULL)
{
strcat(featureList,*features);
strcat(featureList,"$$&&#&&$$");
features++;
}
lc_log(lm_job, featureList);
return featureList;
}
The problem was that "featurelist" variable in the C code was null, and the below code was not enough to handle it.
while(*features != NULL)
Adding another check using the below code, avoids the crash.
features != NULL
As for the error occurring only when not running as "Network Service", the 3rd Party code (lc_feat_list) depended on an user specific registry entry in HKU, whereas the documentation incorrectly refers to a static registry entry under HKLM, which ultimately caused it to return a null value.

Change wallpaper with service

I'm trying to use a service installed to run as a specific user (me in this case) to change the wallpaper.
Here is my Wallpaper class which has a SetWallpaper function:
public sealed class Wallpaper
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError=true)]
private static extern Int32 SystemParametersInfo(
UInt32 action, UInt32 uParam, String vParam, UInt32 winIni);
private static readonly UInt32 SPI_SETDESKWALLPAPER = 0x14;
private static readonly UInt32 SPIF_UPDATEINIFILE = 0x01;
private static readonly UInt32 SPIF_SENDWININICHANGE = 0x02;
public static void SetWallpaper(String path)
{
System.IO.Stream s = new System.Net.WebClient().OpenRead(path.ToString());
System.Drawing.Image img = System.Drawing.Image.FromStream(s);
string tempPath = Path.Combine(Path.GetTempPath(), "wallpaper.bmp");
ImgurWallpaperSetter.ImgurWallpaperSetter.log(tempPath);
img.Save(tempPath, System.Drawing.Imaging.ImageFormat.Bmp);
SystemParametersInfo(SPI_SETDESKWALLPAPER, 1, tempPath,
SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
int error = Marshal.GetLastWin32Error();
ImgurWallpaperSetter.ImgurWallpaperSetter.log("Last error: " + error);
}
}
It works perfectly when I run SetWallpaper from a unit test, but it doesn't work at all when I install the service and start it.
Here is the service start code:
protected override void OnStart(string[] args) {
//WallpaperScheduler.ScheduleWallpaperFetch(DateTime.Now.Hour, DateTime.Now.Minute+1);
//Debugger.Launch();
Uri imageUrl = WallpaperRetriever.mostPopularImgurWallpaper();
log(imageUrl.AbsoluteUri);
Wallpaper.SetWallpaper(imageUrl.AbsoluteUri);
}
I've confirmed that it's downloading the image into my temp directory correctly, but it's not setting the wallpaper. It doesn't error out or log anything to the event logs.
Here's my service installed in the local service viewer:
Running it does nothing.
A similar thread I've read
Edit:
Added this code to run on my serviceInstaller_Committed event which should allow the service to interact with the desktop, but I see a huge delay between the service run and the actual switching of the wallpaper:
ConnectionOptions coOptions = new ConnectionOptions();
coOptions.Impersonation = ImpersonationLevel.Impersonate;
ManagementScope mgmtScope = new ManagementScope(#"root\CIMV2", coOptions);
mgmtScope.Connect();
ManagementObject wmiService;
wmiService = new ManagementObject(
"Win32_Service.Name='" + serviceInstaller1.ServiceName + "'"
);
ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
InParam["DesktopInteract"] = true;
ManagementBaseObject OutParam = wmiService.InvokeMethod("Change", InParam, null);
Edit2:
I've updated my service to log to the system events GetLastError(). Now I am seeing that the service is throwing error 1459 ("This operation requires an interactive window station."). However, this doesn't explain why my wallpaper does eventually switch (I think usually after waking from sleep). Updated Wallpaper class above as well.
Edit3
I've confirmed that after sleeping, the new wallpaper is set. Can anyone explain why this is? Could it be that I need to restart for the Interactive Desktop ability to be set?
Edit4
What I'm doing is feeling pretty hacky. Would it be better if I had the service do nothing but download wallpapers and potentially had another non-service application for changing the wallpaper if new wallpapers have been downloaded and the user is logged in?
Are you aware of Session 0 Isolation? It means that your service is running in a desktop that no user will ever log into, and that restricted environment may very well be affecting your program's behavior.
You say that the code "doesn't error out or log anything to the event logs", but, based on what you have shown, you need to improve the error checking to catch more subtle problems. For example, SystemParametersInfo() returns FALSE when it fails (and a subsequent call to GetLastError() may be very informative!) but your code doesn't check for that result. You can not rely on explicit exceptions alone.
Add this class: http://pastebin.com/ERsnqMEy
Use it like this: http://pastebin.com/RYvvT7bH
Works wonders in using WMI impersonating the logged in user from a windows system service. Best of luck.
In Services, go to Properties, and check "Use Local Account" and something like "Allow Using of Desktop". I'm not sure about names, because my Windows is in a different language, but you should be able to find it.

Active Directory userAccountControl null after otherwise valid search

and thanks for reading.
I have tried many variations of the following code, and all have returned a null value for the userAccountControl property:
DirectoryEntry de = new DirectoryEntry("LDAP://{my server/domain}");
DirectorySearcher ds= new DirectorySearcher(de);
ds.Filter = "(&(objectClass=user)(objectGUID=" + queryGUID + "))";
ds.PropertiesToLoad.Add("userAccountControl");
foreach (SearchResult sr in ds.FindAll())
{
var userFlags = sr.GetDirectoryEntry().Properties["userAccountControl"].Value;
int flags = (int)userFlags;
bool enabled = (flags & 0x2) == 0x2;
Console.WriteLine("Enabled: {0}", enabled ? "true" : "false");
}
Currently it's filtering using an objectGuid I retrieve from a valid user, converted into the proper form. (Being a test program I don't care about the string concatenation...I'll fix that in production code later.) I could (and have) use(d) other search filter values, including bitwise transitive filters. I've used direct binding versus a directory search. I've written a dozen or more variations of this, and all with the same result: the query succeeds but the userFlags property itself comes back null (is not present).
Since I'm specifically asking for a user class here, I know I'm not inadvertently getting a contact class (which wouldn't have the userAccountControl property). The bitwise operations shown in the code aren't important (I know I can convert to an enum and compare that way). It crashes with a null reference exception before the bitwise operations anyway.
This is running on Windows Server 2008 R2, using .NET 4 (I know of the issue with .NET 4.5 and AD account management). The account running this has both Administrator and Enterprise Administrator privileges. Also, as an aside, I downloaded Softerra's LDAP administrator console, and it as well doesn't show this property as present.
My question is simply why is this value null? It should not be, to my limited knowledge. Did I not set AD up properly in the beginning, perhaps? The search is improperly constructed?
Is your code find user? If it s true can u try that?
//...
var results = ds.FindAll();
foreach (SearchResult sResult in results)
{
var directoryEntry = sResult.GetDirectoryEntry();
using (directoryEntry)
{
bool enabled;
const string attrib = "userAccountControl";
const int ufAccountDisable = 0x0002;
de.RefreshCache(new string[] { attrib });
var flags =(int)de.Properties[attrib].Value;
if (((flags & ufAccountDisable) == ufAccountDisable))
{
enabled = false;
}
else
{
enabled true;
}
}
}
I use this code block. I tried it, after set disable a user in active directory. It must works correctly.
Found it. It turns out that new user attributes are protected and can be accessed only by code running as administrator ("Run As"). I wasn't originally running this code using elevated privilege. Once I did, the attributes appeared. This seems to be similar behavior to querying for tombstoned objects. All I can say is "Doh!" :)

Access denied while getting process path

I am trying to get process path by pid but I'm getting Win32Exception (access id denied).
The code looks like this:
string path = Process.GetProcessById(pid).MainModule.FileName
I have tried using OpenProcess with GetModuleFileNameEx but OpenProcess is returning 0. I even tried enabling SeDebugPrivilege according to C# – How to enable SeDebugPrivilege but it didn't help.
The above code works for most of the processes but throws error for SynTPHelper.exe (Synaptics Pointing Device Helper) The application is running under the same username as my code. Both, my application and the process run in 64 bit.
Is it possible to retrieve the path without running my application as an administrator?
Edit
Task Manager is able to 'open file location' even though I'm not running it as an administrator.
Finally I managed to solve it. As it turned out there is new function in Vista and above for getting process path and new process access (PROCESS_QUERY_LIMITED_INFORMATION):
QueryFullProcessImageName
Here is the code that works from non-elevated process:
private static string GetExecutablePathAboveVista(UIntPtr dwProcessId)
{
StringBuilder buffer = new StringBuilder(1024);
IntPtr hprocess = OpenProcess(ProcessAccessFlags.PROCESS_QUERY_LIMITED_INFORMATION, false, dwProcessId);
if (hprocess != IntPtr.Zero)
{
try
{
int size = buffer.Capacity;
if (QueryFullProcessImageName(hprocess, 0, buff, out size))
{
return buffer.ToString();
}
}
finally
{
CloseHandle(hprocess);
}
}
return string.Empty;
}
Well, it is certainly not unheard of for services to remove access rights so that even an administrator cannot open the process. A service has enough privileges to do so, DRM components like audiodg.exe readily do so. A mouse pad helper doesn't strike me as something that would require such protection. But what the hey, why would anybody ever need to mess with a mouse pad helper?

Launching GUI App from Windows Service - Window Does Not Appear

I have written a simple windows service which will launch a exe specified in the
onstart() method of the service. After starting the service the exe got launched it only
presents in the memory but it doesnt show in the explorer.
I'm trying to launch a calc.exe from my code.it shows the exe in the memory but it
doesnt comes into my view(i.e) in the explorer.
Below is my code to launch the exe in the onStart() method
Process pr=new Process();
pr.StartInfo.FileName="calc.exe";
pr.StartInfo.WindowStyle=ProcessWindowStyle.Maximized;
pr.StartInfo.CreateNoWindow=false;
pr.Start();
// pr.WaitForExit();
Services run in other session on Vista or later and applications started directly from services are started in the same session by default. Starting applications in other sessions is possible - you have to find the id of the user session and use CreateProcessAsUser.
If more than one user is logged in and you need to start your program for all users you must find the ids of all sessions.
Here is sample code:
int session = Win32.WTSGetActiveConsoleSessionId();
if (session == 0xFFFFFFFF)
{
return false;
}
IntPtr userToken;
bool res = Win32.WTSQueryUserToken(session, out userToken);
if (!res)
{
this.log.WriteEntry("Error WTSQueryUserToken");
return false;
}
string path = GetPath();
string dir = Path.GetDirectoryName(path);
Win32.STARTUPINFO si = new Win32.STARTUPINFO();
si.lpDesktop = "winsta0\\default";
si.cb = Marshal.SizeOf(si);
Win32.PROCESS_INFORMATION pi = new Win32.PROCESS_INFORMATION();
Win32.SECURITY_ATTRIBUTES sa = new Win32.SECURITY_ATTRIBUTES();
sa.bInheritHandle = 0;
sa.nLength = Marshal.SizeOf(sa);
sa.lpSecurityDescriptor = IntPtr.Zero;
if (!Win32.CreateProcessAsUser(userToken, // user token
path, // exexutable path
string.Empty, // arguments
ref sa, // process security attributes ( none )
ref sa, // thread security attributes ( none )
false, // inherit handles?
0, // creation flags
IntPtr.Zero, // environment variables
dir, // current directory of the new process
ref si, // startup info
out pi)) // receive process information in pi
{
int error = Marshal.GetLastWin32Error();
this.log.WriteEntry("Error CreateProcessAsUser:" + error);
return false;
}
Services are run under different account privileges (LocalService/NetworkService etc)and hence they don't have access to your desktop (under your login account's control).
Services are meant to do their job silently and thats what they should do. (with the exception of logging something in windows event log when they have something important to say)
If you open your service's properties window, go to the Log On tab then check the "Allow service to interact with desktop" check box you will get the behavior you want. Also depending on what app you what to run you may need to change the log on account.
Services are not interactive by definition, so you shouldn't expect any user interface elements to show when you launch an application from a service.
It's by design...
Like already mentioned from the others a windows service is "normally" running under a separate account ("LocalSystem" or "NetworkService"). This is the reason why you might no see the UI of the program started by your service. Also services are not intended to have a UI, they act as a background service.
But also note that starting a application by a service can be a high security risk, because the application is running with the same privileges than your service is. Normally this would be the local system account.
I don't know what your are trying to achieve with your service, but consider to use the autostart function of windows instead of a service to run your application.

Categories

Resources