Enumerating list of drives on a remote domain workstation - c#

I am trying to come up with a good way to enumerate hard disks on remote workstations, possibly including administrative shares, so I can audit key files on them without having to access them via sneakernet. I have domain administrator rights. Security policy prohibits using WMI which would be a great solution.
I can retrieve a list of computers using Active Directory, but I need some way to determine what drives are available on each system. Is such a thing feasible? A fellow developer offered some VB6 code from years ago that used WNetOpenEnum, but I was hoping that since we are at .NET framework 4, maybe there's a more elegant / managed way of working with this.
Any ideas would be much appreciated!
EDIT:
I'm keen to use technologies that are more generally supported, such as standard APIs etc. WMI is a great solution but apparently is blocked by default by Windows Firewall, so its availability is not guaranteed.

Add a reference to System.Management, then:
using System;
using System.Management;
namespace WmiConnectRemote
{
class Program
{
static void Main(string[] args)
{
var machine = "XXXX";
var options = new ConnectionOptions { Username = "XXXX", Password = "XXXX" };
var scope = new ManagementScope(#"\\" + machine + #"\root\cimv2", options);
var queryString = "select Name, Size, FreeSpace from Win32_LogicalDisk where DriveType=3"; var query = new ObjectQuery(queryString);
var worker = new ManagementObjectSearcher(scope, query);
var results = worker.Get();
foreach (ManagementObject item in results)
{
Console.WriteLine("{0} {2} {1}", item["Name"], item["FreeSpace"], item["Size"]);
}
}
}
}

Related

How to check a unit (C:) dirty bit using C#

I know it is possible to check the dirty bit status of a unit by running the command fsutil dirty query c: from an elevated prompt. On windows 10 it is also possible to know if C: dirty bit is set without the need of admin privileges simply going into the System and Maintenance page, if dirty bit is set there will be an advice telling it is necessary to reboot in order to repair a damage in the file sistem. How could the dirty bit status (of any unit or even only C:) be checked from a C# program?
Thanks in Advance to anyone will answer
You can get this information using a WMI query
var q = new ObjectQuery("Select * FROM Win32_Volume");
using (var searcher = new ManagementObjectSearcher(q))
using (var moc = searcher.Get())
{
foreach (ManagementObject volume in moc)
{
String label = (String)volume["Label"];
Boolean dirtyBitSet = (Boolean)(volume["DirtyBitSet"] ?? false);
Console.WriteLine($"{label} => {dirtyBitSet}");
}
}
You should add a reference to the System.Management assembly and also run your program using an elevated prompt

Use Exchange Recipient Update Service via c# without PowerShell

I'm searching to get the current members of a dynamic distribution group by exchange servers. Dynamic distribution groups are based on a specified filter. The "Recipient Update Service" (RUS) find each contact by runtime, based on this filter.
I've found a lot of information to solve the problem by using a wrapper class of exchange powershell in interaction of classic commandline arguments. But this is not my intended way.
I thought there should be a special command of "Exchange Web Services" (EWS) to get the dynamic members by runtime or by interop. I was unable to find some information about this.
Does anybody have an idea or some information to solve this problem via c#?
DirectoryServices seems to do the trick for me. Create a DirectoryEntry pointing to the dynamic distribution list (schema class name = "msExchDynamicDistributionList"), and then use the "msExchDynamicDLBaseDN" and "msExchDynamicDLFilter" properties to search for the members:
using (var group = new DirectoryEntry("LDAP://CN=MyGroup,OU=MyOU,DC=company,DC=com"))
{
string baseDN = (string)group.Properties["msExchDynamicDLBaseDN"].Value;
string filter = (string)group.Properties["msExchDynamicDLFilter"].Value;
using (var searchRoot = new DirectoryEntry("LDAP://" + baseDN))
using (var searcher = new DirectorySearcher(searchRoot, filter, propertiesToLoad))
using (var results = searcher.FindAll())
{
foreach (SearchResult result in results)
{
// Use the result
}
}
}
Remember that the members of the group could be regular groups or other dynamic distribution groups as well as users, contacts and public folders.

Is there really any way to uniquely identify any computer at all

I know there are a number of similar questions in stackoverflow such as the followings:
What's a good way to uniquely identify a computer?
What is a good unique PC identifier?
Unique computer id C#
WIN32_Processor::Is ProcessorId Unique for all computers
How to uniquely identify computer using C#?
... and dozens more and I have studied them all.
The problem is that some of the accepted answers have suggested MAC address as an unique identifier which is entirely incorrect. Some other answers have suggested to use a combination of various components which seems more logical. However, in case of using a combination it should be considered which component is naturally unlikely to be changed frequently. A few days ago we developed a key generator for a software licensing issue where we used the combination of CPUID and MAC to identify a windows pc uniquely and till practical testing we thought our approach was good enough. Ironically when we went testing it we found three computers returning the same id with our key generator!
So, is there really any way to uniquely identify any computer at all? Right now we just need to make our key generator to work on windows pc. Some way (if possible at all) using c# would be great as our system is developed on .net.
Update:
Sorry for creating some confusions and an apparently false alarm. We found out some incorrectness in our method of retrieving HW info. Primarily I thought of deleting this question as now my own confusion has gone and I do believe that a combination of two or more components is good enough to identify a computer. However, then I decided to keep it because I think I should clarify what was causing the problem as the same thing might hurt some other guy in future.
This is what we were doing (excluding other codes):
We were using a getManagementInfo function to retrieve MAC and Processor ID
private String getManagementInfo(String StrKey_String, String strIndex)
{
String strHwInfo = null;
try
{
ManagementObjectSearcher searcher = new ManagementObjectSearcher("select * from " + StrKey_String);
foreach (ManagementObject share in searcher.Get())
{
strHwInfo += share[strIndex];
}
}
catch (Exception ex)
{
// show some error message
}
return strHwInfo;
}
Then where needed we used that function to retrieve MAC Address
string strMAC = getManagementInfo("Win32_NetworkAdapterConfiguration", "MacAddress");
and to retrieve ProcessorID
string strProcessorId = getManagementInfo("Win32_Processor", "ProcessorId");
At this point, strMAC would contain more than one MAC address if there are more than one. To take only one we just took the first 17 characters (12 MAC digits and 5 colons in between).
strMAC = strMAC.Length > 17 ? strMAC.Remove(17) : strMAC;
This is where we made the mistake. Because getManagementInfo("Win32_NetworkAdapterConfiguration", "MacAddress") was returning a number of extra MAC addresses that were really in use. For example, when we searched for MAC addresses in the command prompt by getmac command then it showed one or two MAC addresses for each pc which were all different. But getManagementInfo("Win32_NetworkAdapterConfiguration", "MacAddress") returned four to five MAC addresses some of which were identical for all computers. As we just took the first MAC address that our function returned instead of checking anything else, the identical MAC addresses were taken in strMAC incidently.
The following code by Sowkot Osman does the trick by returning only the first active/ enabled MAC address:
private static string macId()
{
return identifier("Win32_NetworkAdapterConfiguration", "MACAddress", "IPEnabled");
}
private static string identifier(string wmiClass, string wmiProperty, string wmiMustBeTrue)
{
string result = "";
System.Management.ManagementClass mc = new System.Management.ManagementClass(wmiClass);
System.Management.ManagementObjectCollection moc = mc.GetInstances();
foreach (System.Management.ManagementObject mo in moc)
{
if (mo[wmiMustBeTrue].ToString() == "True")
{
//Only get the first one
if (result == "")
{
try
{
result = mo[wmiProperty].ToString();
break;
}
catch
{
}
}
}
}
return result;
}
//Return a hardware identifier
private static string identifier(string wmiClass, string wmiProperty)
{
string result = "";
System.Management.ManagementClass mc = new System.Management.ManagementClass(wmiClass);
System.Management.ManagementObjectCollection moc = mc.GetInstances();
foreach (System.Management.ManagementObject mo in moc)
{
//Only get the first one
if (result == "")
{
try
{
result = mo[wmiProperty].ToString();
break;
}
catch
{
}
}
}
return result;
}
However, I was absolutely right about the identical Processor ID issue. All three returned the same Processor ID when we put wmic cpu get ProcessorId command in their command prompts.
Now we have decided to use Motherboard serial number instead of Processor ID to make a combination with MAC address. I think our purpose will be served with this way and if it doesn't in some cases then we should let it go in those few cases.
How about adding motherboard serial number as well e.g.:
using System.management;
//Code for retrieving motherboard's serial number
ManagementObjectSearcher MOS = new ManagementObjectSearcher("Select * From Win32_BaseBoard");
foreach (ManagementObject getserial in MOS.Get())
{
textBox1.Text = getserial["SerialNumber"].ToString();
}
//Code for retrieving Processor's Identity
MOS = new ManagementObjectSearcher("Select * From Win32_processor");
foreach (ManagementObject getPID in MOS.Get())
{
textBox2.Text = getPID["ProcessorID"].ToString();
}
//Code for retrieving Network Adapter Configuration
MOS = new ManagementObjectSearcher("Select * From Win32_NetworkAdapterConfiguration");
foreach (ManagementObject mac in MOS.Get())
{
textBox3.Text = mac["MACAddress"].ToString();
}
The fact in getting a globally unique ID is, only MAC address is the ID that will not change if you set up your system all over. IF you are generating a key for a specific product, the best way to do it is assigning unique IDs for products and combining the product ID with MAC address. Hope it helps.
I Completely agree with just the above comment.
For Software licensening, you can use:
Computer MAC Address (Take all if multiple NIC Card) + Your software Product Code
Most of the renowned telecom vendor is using this technique.
However, I was absolutely right about the identical Processor ID
issue. All three returned the same Processor ID when we put wmic cpu
get ProcessorId command in their command prompts.
Processor ID will be same if all the systems are running as virtual machines on the same hypervisor.
MAC ID seems fine. Only thing is users must be provided the option to reset the application, in case the MAC changes.
It looks like custom kitchen is the way for that.
SMBIOS UUID (motherboard serial) is not robust, but works fine in 99% cases. However some brands will set the same UUID for multiple computers (same production batch maybe). Getting it requires WMI access for the user (if he's not administrator), you can solve that by starting an external process asking administrator priviledges (check codeproject.com/Articles/15848/WMI-Namespace-Security)
Windows Product ID might be good, but I read it could be identical in some circumstances (https://www.nextofwindows.com/the-best-way-to-uniquely-identify-a-windows-machine)
Could someone clarify if the same Product ID (not product key) might be present on multiple computers ?
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MachineGuid seems interesting. It's generated when installing Windows and if changed, it requires to reactivate Windows.
Mac Addresses are interresting but you can only take the first one or your unique ID will change when the interface is disabled, or when another network interface is added and appears first etc.
Hard Drive serial number is nice but when installing a ghost, it might also override the serial number from the original drive... And the HD serial is very easy to change.
The best might be to generate an ID with a combination of those machine identifiers and decide if the machine is the same by comparing those identifiers (ie if at least one Mac address + either SMBIOS UUID or Product ID is ok, accept)

How to expand environment variables remotely with .NET?

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...

Connecting to LDAP from C# using DirectoryServices

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.

Categories

Resources