We have an application that installs SQL Server Express from the command line and specifies the service account as the LocalSystem account via the parameter SQLACCOUNT="NT AUTHORITY\SYSTEM".
This doesn't work with different languages because the account name for LocalSystem is different. There's a table listing the differences here:
http://forums.microsoft.com/MSR/ShowPost.aspx?PostID=685354&SiteID=37
This doesn't seem to be complete (the Swedish version isn't listed). So I'd like to be able to determine the name programmatically, perhaps using the SID?
I've found some VB Script to do this:
Set objWMI = GetObject("winmgmts:root\cimv2")
Set objSid = objWMI.Get("Win32_SID.SID='S-1-5-18'")
MsgBox objSid.ReferencedDomainName & "\" & objSid.AccountName
Does anyone know the equivalent code that can be used in C#?
You can use .NET's built-in System.Security.Principal.SecurityIdentifier class for this purpose: by translating it into an instance of NtAccount you can obtain the account name:
using System.Security.Principal;
SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(acct.Value);
Later edit, in response to question in comments: you do not need any special privileges to do SID-to-name lookups on the local machine -- for example, even if the user account you're running under is only in the Guests group, this code should work. Things are a little bit different if the SID resolves to a domain account, but even that should work correctly in most cases, as long as you're logged on to the domain (and a domain controller is available at the time of the lookup).
Or you can use:
string localSystem = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null).Translate(typeof(NTAccount)).Value;
With WellKnownSidType you can look for other accounts, as NetworkService for example.
This should do something similar to what you posted. I'm not sure how to get specific properties of WMI objects offhand, but this will get you started with the syntax:
ManagementObject m = new ManagementObject("winmgmts:root\cimv2");
m.Get();
MessageBox.Show(m["Win32_SID.SID='S-1-5-18'"].ToString());
The problem with the accepted answer is that the account name must be resolvable by the local machine running the code.
If you are reading the ACLs on a remote machine you may well not be able to resolve Domain SIDs / local SIDs on the remote box. The following uses WMI and takes the parameter of the remote machine and the SID you want the remote machine to resolve.
/// <summary>
/// Returns the Account name for the specified SID
// using WMI against the specified remote machine
/// </summary>
private string RemoteSID2AccountName(String MachineName, String SIDString)
{
ManagementScope oScope = new ManagementScope(#"\\" + MachineName +
#"\root\cimv2");
ManagementPath oPath = new ManagementPath("Win32_SID.SID='" + SIDString + "'");
ManagementObject oObject = new ManagementObject(oScope, oPath, null);
return oObject["AccountName"].ToString();
}
Related
I have a windows application. In that i have retrieved appdata using environment variable. So it gives me following path
c:\document and settings\current user name\application data.
But when I retrieve the appdata path from windows service using environment variable i get following path
c:\windows\ServiceProfiles\LocalService\AppData\Local
so this appdata path is different from appdata path that i got from windows application environments variable appdata path.
I m running windows service under local profile. I know that if i change service profile to run under user then service appdata path and windows application appdata path matches but service prompts for username and password.
so my question is how to get user appdata path from service by running service under local profile without prompting for username and password?
I have also encountered in that problem and looked at your question but i at first sight didnot find an answer in it.
This is the Mohit shah Answer
"I found out that is not possible to get user appdata path from windows service by running service under profile "LocalSystem". So I used Environment.SpecialFolder.CommomAppData which gives me app data path C:\ProgramData when run on windows 7 and when used same thing in windows service, it also gived the same path and i also ran the service under profile "LocalSystem" so it did not prompt me for credentials. So this solved my problem."
#Mohit Shah Please mark this is as answer so that other can take help from that.
Windows service will always runs on SYSTEM level and hence it wont able to access user specific folder. Either as #ovais suggested you can store user data inside program data folder or you can use following approach.
You can use Windows management API's to get the current windows user name.Usually remaining path will be constant and hence you can construct remaining path.
Say for example, data is stored inside - "C:\Users\xyzUser\appdata\roaming..."
Only thing which is not constant here is "xyzUser" and "C"(User can install in different drives).
public static string GetWindowsUserAccountName()
{
string userName = string.Empty;
ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
ObjectQuery query = new ObjectQuery("select * from win32_computersystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(ms, query);
foreach (ManagementObject mo in searcher?.Get())
{
userName = mo["username"]?.ToString();
}
userName = userName?.Substring(userName.IndexOf(#"\") + 1);
return userName;
}
Drawback of this approach is, when you connected through remote connection, username will give you "NULL". So please be careful while using .
Windows folder you can get through following snippet.
public static string GetWindowsFolder()
{
string windowsFolder = string.Empty;
ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
ObjectQuery query = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(ms, query);
foreach (ManagementObject m in searcher?.Get())
{
windowsFolder = m["WindowsDirectory"]?.ToString();
}
windowsFolder = windowsFolder.Substring(0, windowsFolder.IndexOf(#"\"));
return windowsFolder;
}
I am working on LDAP, I have installed OpenLDAP on my local machine and created a RootDSE as dc=wave,dc=com.
and I am trying to fetch this baseDN using this code:
DirectoryEntry directoryEntry = new DirectoryEntry(string.Format("LDAP://{0}", server), "CN=Manager,dc=wave,dc=com", "secret");
string dnPath;
try
{
dnPath = directoryEntry.Properties["distinguishedName"].Value.ToString();
}
catch
{
dnPath = "";
}
but it always fall in exception, also why do I need to pass whole dn for username?
same thing if I try with Active Directory server it works perfectly, also I don't need to pass the whole dn for username.
any Idea?
The System.DirectoryServices namespace really only works with Active Directory. You should switch over to using System.DirectoryServices.Protocols when working with any other directory. You'll probably find that its even better when working with AD as well.
I need a way to expand environment variable on a remote machine.
Suppose I have a path to a folder %appdata%\MyApp\Plugins or %ProgramFiles%\MyCompany\MyApp\Plugins and I want to list files in that folder for audit purposes. The only problem is I want to do it on a remote machine, which however I have admin access to.
An extra question (but not essential) is how to do that for given user on remote machine?
You would use GetFolderPath. There are a bunch of different SpecialFolder values that you could use including ProgramFiles and ApplicationData
string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
Then you could just combine it with the rest of your path
string full_path = Path.Combine(path, "\MyApp\Plugins");
On a remote machine, it looks like you can try something like this
ConnectionOptions co = new ConnectionOptions();
// user with sufficient privileges to connect to the cimv2 namespace
co.Username = "administrator";
// his password
co.Password = "adminPwd";
ManagementScope scope = new ManagementScope(#"\\BOBSMachine\root\cimv2", co);
SelectQuery query = new SelectQuery("Select windowsdirectory from Win32_OperatingSystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
foreach (ManagementObject windir in searcher.Get())
Console.WriteLine("Value = {0}", windir["windowsdirectory"]);
Or for a list of all remote environment variables and their values, from here
public static void GetSysInfo(string domain, string machine, string username, string password)
{
ManagementObjectSearcher query = null;
ManagementObjectCollection queryCollection = null;
ConnectionOptions opt = new ConnectionOptions();
opt.Impersonation = ImpersonationLevel.Impersonate;
opt.EnablePrivileges = true;
opt.Username = username;
opt.Password = password;
try
{
ManagementPath p = new ManagementPath("\\\\" +machine+ "\\root\\cimv2");
ManagementScope msc = new ManagementScope(p, opt);
SelectQuery q= new SelectQuery("Win32_Environment");
query = new ManagementObjectSearcher(msc, q, null);
queryCollection = query.Get();
Console.WriteLine(queryCollection.Count);
foreach (ManagementBaseObject envVar in queryCollection)
{
Console.WriteLine("System environment variable {0} = {1}",
envVar["Name"], envVar["VariableValue"]);
}
}
catch(ManagementException e)
{
Console.WriteLine(e.Message);
Environment.Exit(1);
}
catch(System.UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
Environment.Exit(1);
}
}
OP Edit:
Also %AppData% can be found from registry (can be done remotely) at HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders and Program Files at HKLM\Software\Microsoft\Windows\CurrentVersion, under ProgramfilesDir.
The question doesn't make sense. Environment variables are not per-machine variables. For instance, you can expect %appdata% to point inside the C:\users\ directory, but precisely where obviously depends to the user. Logging in as admin still doesn't help you; that would merely tell you where the admin's %appdata% is.
Environment variables are the amalgamation of 'puter-wide and per-user settings. A running process may modify its environment and when it spawns another process, that process inherits the environment of the process that created it.
Unless you have access to a process running on the remote machine (or can start one), there's no such thing as an 'environment': the context for it simply doesn't exist. The environment of a particular process is a function of all of the following:
the environment inherited from the parent process' environment (which may be running under a different user account than the child process.)
computer-wide environment settings.
any environment settings specified by the user.
any changes made by the process itself.
That being said, Windows keeps its environment variable settings in the registry:
User variables.HKEY_CURRENT_USER\Environment
System variables.HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
If you have appropriate access to the remote machine's registry, you should be able to fish out what you need.
Note that environment variables may be defined in terms of other environment variables: I believe you'll likely to take care of the proper expansion yourself.
As far as I can tell, the only way of resolving %ProgramFiles% is via the registry, since this is not exposed in Win32_Environment (despite the documentation suggesting otherwise). So this works fine:
$key = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$serverName);
$versionKey = $key.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion');
$versionKey.GetValue('ProgramFilesDir')
However, I can't appear to use this approach to get back the Program Files (x86) folder - the key I can see in the registry doesn't 'show' using the registry API. Strange.
Of course if you were running Powershell Remoting on the remote machine, I imagine this would be fairly easy...
I am trying to connect to an edirectory 8.8 server running LDAP. How would I go about doing that in .Net? Can I still use the classes in System.DirectoryService such as DirectoryEntry and DirectorySearcher or are they AD specific? Do I need to specify the "Connection String" any differently?
I am trying something like the code below but it doesn't seem to work...
DirectoryEntry de = new DirectoryEntry ("LDAP://novellBox.sample.com","admin","password",AuthenticationTypes.None);
DirectorySearcher ds = new DirectorySearcher(de);
var test = ds.FindAll();
Any ideas?
Well, I think your connection string is missing a bit - specifying just the server name isn't good enough - you also need to specify a "starting point" for your search.
In AD, this would typically be something like the "Users" container in your domain, which you'd specify like this in LDAP parlance:
LDAP://novellBox.sample.com/cn=Users,dc=YourCompany,dc=com
Not sure how LDAP compliant the newer versions of eDirectory are - but that should work since in theory, it's standard LDAP regardless of the implementation :-)
But then again: only in theory, there's no difference between theory and practice.....
There's also a System.DirectoryServices.Protocols namespace which offers low-level LDAP calls directly - and that's definitely not tied to AD at all, but it's really quite low-level.....
There's also a Novell C# LDAP library but I've never tried it and can't say how complete or capable it is. It might give you some clues, though!
Also see this other Stackoverflow question about Novell, LDAP and C# - it might give you additional info.
I had a hard time figuring this out but you could use something like the following, it worked sweet for me:
Domain domain = Domain.GetDomain(new DirectoryContext(DirectoryContextType.Domain, "novellBox.sample.com");
DirectorySearcher ds = new DirectorySearcher(domain.GetDirectoryEntry(), searchQuery);
using (SearchResultCollection src = ds.FindAll())
{....}
I think you need to use LDAP syntax for the host.
Make sure you don't forget to release the connection with using - if you don't dispose of the directory entries they hang around forever until the pool runs out and your app breaks.
using (DirectoryEntry de = new DirectoryEntry ("LDAP://CN=server,DC=domain,DC=com","admin","password",AuthenticationTypes.Secure))
{
...
}
Depending ont he directory server configuration, you might actually need to use the System.DirectoryServices.Protocols namespace. I wrote up a post on connecting to OpenLDAP with it.
http://mikemstech.blogspot.com/2013/03/searching-non-microsoft-ldap.html
If the external LDAP require authentication with DN try this: first retrieve the DN of user, then try the authentication with DN and user credentials. I've tested it on Domino LDAP.
// Autheticate in external LDAP
string ldapserver = "10.1.1.1:389";
string ldapbasedn = "o=mycompany";
string ldapuser = "cn=Administrator,o=mycompany";
string ldappassword = "adminpassword";
string ldapfilter = "(&(objectclass=person)(cn={0}))";
string user = "usertest";
string password = "userpassword";
try
{
string DN = "";
using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + ldapserver + "/" + ldapbasedn, ldapuser, ldappassword, AuthenticationTypes.None))
{
DirectorySearcher ds = new DirectorySearcher(entry);
ds.SearchScope = SearchScope.Subtree;
ds.Filter = string.Format(ldapfilter, user);
SearchResult result = ds.FindOne();
if (result != null )
{
DN = result.Path.Replace("LDAP://" + ldapserver + "/" , "");
}
}
// try logon
using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + ldapserver + "/" + ldapbasedn, DN, password, AuthenticationTypes.None))
{
DirectorySearcher ds = new DirectorySearcher(entry);
ds.SearchScope = SearchScope.Subtree;
SearchResult result = ds.FindOne();
}
} catch (Exception) { }
I am trying to connect to an edirectory 8.8 server running LDAP. How would I go about doing that in .Net? Can I still use the classes in System.DirectoryService such as DirectoryEntry and DirectorySearcher or are they AD specific?
We are using System.DirectoryServices for Microsoft Active Directory, OpenLDAP running on Linux and eDirectiry without any problem. So the answer is yes, you can use these classes to access eDir.
Do I need to specify the "Connection String" any differently?
Yes you are. When passing to DirectoryEntry a string starting with "LDAP://" you need to conform to the LDAP syntax which is very different than URI syntax.
I recommend you to use an LDAP browser (google it, there are many free downloads) in order to get the correct path to the root object otherwise you will spend time on trying to figure out the correct object types.
I have a C# application that scans a directory and gathers some information. I would like to display the account name for each file. I can do this on the local system by getting the SID for the FileInfo object, and then doing:
string GetNameFromSID( SecurityIdentifier sid )
{
NTAccount ntAccount = (NTAccount)sid.Translate( typeof( NTAccount ) );
return ntAccount.ToString();
}
However, this does not work for files on a network, presumably because the Translate() function only works with local user accounts. I thought maybe I could do an LDAP lookup on the SID, so I tried the following:
string GetNameFromSID( SecurityIdentifier sid )
{
string str = "LDAP://<SID=" + sid.Value + ">";
DirectoryEntry dirEntry = new DirectoryEntry( str );
return dirEntry.Name;
}
This seems like it will work, in that the access to "dirEntry.Name" hangs for a few seconds, as if it is going off and querying the network, but then it throws a System.Runtime.InteropServices.COMException
Does anyone know how I can get the account name of an arbitrary file or SID? I don't know much about networking or LDAP or anything. There's a class called DirectorySearcher that maybe I'm supposed to use, but it wants a domain name, and I don't know how to get that either - all I have is the path to the directory I'm scanning.
See here for a good answer:
The best way to resolve display username by SID?
The gist of it is this bit:
string sid="S-1-5-21-789336058-507921405-854245398-9938";
string account = new System.Security.Principal.SecurityIdentifier(sid).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
This approach works for me for non-local SID's over the active directory.
The SecurityReference object's Translate method does work on non-local SIDs but only for domain accounts. For accounts local to another machine or in a non-domain setup you would need to PInvoke the function LookupAccountSid specifying the specific machine name on which the look up needs to be performed.
System.DirectoryServices.AccountManagement.UserPrincipal class (msdn link) has a static function FindByIdentity to convert an SID to a User object. It should be able to work both against the local machine or an LDAP/Active Directory server. I have only used it against active directory.
Here is an example that I have used in IIS:
// Set the search context to a specific domain in active directory
var searchContext = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "OU=SomeOU,DC=YourCompany,DC=com");
// get the currently logged in user from IIS
MembershipUser aspUser = Membership.GetUser();
// get the SID of the user (stored in the SecurityIdentifier class)
var sid = aspUser.ProviderUserKey as System.Security.Principal.SecurityIdentifier;
// get the ActiveDirectory user object using the SID (sid.Value returns the SID in string form)
var adUser = UserPrincipal.FindByIdentity(searchContext, IdentityType.Sid, sid.Value);
// do stuff to user, look up group membership, etc.
In C#, get the user SID and assign it to a string variable through:
string strUser = System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString();
You will need to use string because the ability to resolve to the UserName supports string. In other words, using var varUser will result in a namespace error.
string strUserName = new System.Security.Principal.SecurityIdentifier(strUser).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
You can also get account name of special accounts like "Everyone" with code like this that will work regardless of user's language settings:
SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
string everyone = everyoneSid.Translate(typeof(System.Security.Principal.NTAccount)).ToString();
Ooh, then it's possible that the LDAP call is not working because you might not be in an Active Directory environment. If this is the case, then each of your machines is responsible for its own identity store. And your first code sample is not working across the network because the machine on which you are executing your code does not know how to resolve the SID that only makes sense on the remote machine.
You really should check if your machines are a part of an Active Directory. You would know this during the logon process. Or you can check by right clicking on "My Computer", select "Properties", the "Computer Name" tab, then see if your computer is part of a domain.
Great. I cribbed some LookupAccountSid() code from here:
http://www.pinvoke.net/default.aspx/advapi32.LookupAccountSid
And that worked, though I had to provide the host name myself. In the case of a UNC path I can just take the first component of it. When it's a mapped drive, I use this code to convert the path to a UNC one:
http://www.wiredprairie.us/blog/index.php/archives/22
It seems to work, so that's how I'll do it, unless someone comes up with a situation in which the first component of a UNC path isn't the host name...
Thank you all for your help.
This one is a stumper. You are in an Active Directory environment right? Just checking:)
Anyhow, instead of binding with sid.Value,
string str = "LDAP://<SID=" + sid.Value + ">";
I would try converting the SID's byte array to an Octet String and bind with that instead.
There is a sweet example here on page 78. This will get you closer. To be honest, I've not tried binding with a SID before. But I've had success binding with a user's GUID though :)
Good luck and let me know how it goes.
Get the current domain:
System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
Get a directory entry from ldap and the domain name:
DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://{0}", domain));
Get the sid from an ActiveDirectoryMembershipProvider ActiveDirectoryMembershipUser:
ActiveDirectoryMembershipUser user = (ActiveDirectoryMembershipUser)Membership.GetUser();
var sid = (SecurityIdentifier)user.ProviderUserKey;
Get the username from the SecurityIdentifier:
(NTAccount)sid.Translate(typeof(NTAccount));
Get directory search done on an activedirectory with the domain directory entry and username:
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = string.Format("(SAMAccountName={0})", username);
search.PropertiesToLoad.Add("Name");
search.PropertiesToLoad.Add("displayName");
search.PropertiesToLoad.Add("company");
search.PropertiesToLoad.Add("homePhone");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("givenName");
search.PropertiesToLoad.Add("lastLogon");
search.PropertiesToLoad.Add("userPrincipalName");
search.PropertiesToLoad.Add("st");
search.PropertiesToLoad.Add("sn");
search.PropertiesToLoad.Add("telephoneNumber");
search.PropertiesToLoad.Add("postalCode");
SearchResult result = search.FindOne();
if (result != null)
{
foreach (string key in result.Properties.PropertyNames)
{
// Each property contains a collection of its own
// that may contain multiple values
foreach (Object propValue in result.Properties[key])
{
outputString += key + " = " + propValue + ".<br/>";
}
}
}
Depending on the data in your active directory, you will get a varied response in the output.
Here is a site that has all the user properties I needed:
For all the Windows developers, the answer is LookupAccountSid
LookupAccountSid(null, Sid, username, userSize, domainName, domainSize, sidType);
I am quite sure you will be able to use the accepted answer from here: Determine the LocalSystem account name using C#
Basically, you can translate an instance of the SecurityIdentifier class to type NTAccount, from which you can get the user name. In code:
using System.Security.Principal;
SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(acct.Value);