listing unsecured shared folders on windows - c#

//ActiveDirectorySearch1
//Displays all computer names in an Active Directory
using System;
using System.DirectoryServices;
namespace ActiveDirectorySearch1
{
class Class1
{
static void Main(string[] args)
{
DirectoryEntry entry = new DirectoryEntry("LDAP://pune");
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = ("(objectClass=computer)");
Console.WriteLine("Listing of computers in the Active Directory");
Console.WriteLine("============================================");
foreach(SearchResult resEnt in mySearcher.FindAll())
{
Console.WriteLine(resEnt.GetDirectoryEntry().Name.ToString()); }
Console.WriteLine("=========== End of Listing =============");
Console.ReadKey();
}
}
}
}
now i just want to list all unsecured shared folders ..what do i do ?

If your AD is pre Windows Server 2008, you should really change your filter to (&(objectCategory=computer)(objectClass=computer)). This will take advantage of indices in the database and be much more efficient.
As to getting Shares, I've used Win32_Share before. You can use mgmtclassgen to build a strongly typed wrapper that's pretty easy to work with.

Related

How can I change several Active Directory user passwords?

I'm trying to programaticaly change several user's password, specifically without using System.DirectoryServices.AccountManagement (PrincipalContext)
I have this piece of working code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.DirectoryServices;
namespace ADScriptService.core
{
class ExportTool
{
const AuthenticationTypes = AuthenticationTypes.Secure | AuthenticationTypes.Sealing | AuthenticationTypes.ServerBind;
private static DirectoryEntry directoryEntry = new DirectoryEntry(ADScriptService.Properties.Settings.Default.ActiveDirectoryPath, ADScriptService.Properties.Settings.Default.ServerAdminUser, ADScriptService.Properties.Settings.Default.ServerAdminPwd, AuthenticationTypes.Secure);
private static DirectorySearcher search = new DirectorySearcher(directoryEntry);
public void Export()
{
string path = ADScriptService.Properties.Settings.Default.ActiveDirectoryPath;
string adminUser = ADScriptService.Properties.Settings.Default.ServerAdminUser;
string adminPassword = ADScriptService.Properties.Settings.Default.ServerAdminPwd;
string userName = "exampleUser";
string newPassword = "P455w0rd";
try
{
search.Filter = String.Format("sAMAccountName={0}", userName);
search.SearchScope = SearchScope.Subtree;
search.CacheResults = false;
SearchResult searchResult = search.FindOne();
if (searchResult == null) Console.WriteLine("User Not Found In This Domain");
DirectoryEntry userEntry = searchResult.GetDirectoryEntry();
userEntry.Path = userEntry.Path.Replace(":389", "");
Console.WriteLine(String.Format("sAMAccountName={0}, User={1}, path={2}", userEntry.Properties["sAMAccountName"].Value, userEntry.Username, userEntry.Path));
userEntry.Invoke("SetPassword", new object[] { newPassword });
userEntry.Properties["userAccountControl"].Value = 0x0200 | 0x10000;
userEntry.CommitChanges();
Console.WriteLine("Se ha cambiado la contraseña");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
This is an example with a single user, but what my program should do is iterate through ~120k users.
However, the operation of setting the search filter, finding one result and getting the DirectoryEntry takes about 2 or 3 seconds per user so I'm trying to use the DirectoryEntries structure given by the DirectoryEntry.Children property, which means replacing the six lines after "try{" with simply DirectoryEntry userentry = directoryEntry.Children.Find("CN=" + userName);
So the example's code would look like this:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.DirectoryServices;
namespace ADScriptService.core
{
class ExportTool
{
const AuthenticationTypes = AuthenticationTypes.Secure | AuthenticationTypes.Sealing | AuthenticationTypes.ServerBind;
private static DirectoryEntry directoryEntry = new DirectoryEntry(ADScriptService.Properties.Settings.Default.ActiveDirectoryPath, ADScriptService.Properties.Settings.Default.ServerAdminUser, ADScriptService.Properties.Settings.Default.ServerAdminPwd, AuthenticationTypes.Secure);
private static DirectorySearcher search = new DirectorySearcher(directoryEntry);
public void Export()
{
string path = ADScriptService.Properties.Settings.Default.ActiveDirectoryPath;
string adminUser = ADScriptService.Properties.Settings.Default.ServerAdminUser;
string adminPassword = ADScriptService.Properties.Settings.Default.ServerAdminPwd;
string userName = "exampleUser";
string newPassword = "P455w0rd";
try
{
DirectoryEntry userEntry = directoryEntry.Children.Find("CN=" + userName);
userEntry.Path = userEntry.Path.Replace(":389", "");
Console.WriteLine(String.Format("sAMAccountName={0}, User={1}, path={2}", userEntry.Properties["sAMAccountName"].Value, userEntry.Username, userEntry.Path));
userEntry.Invoke("SetPassword", new object[] { newPassword });
userEntry.Properties["userAccountControl"].Value = 0x0200 | 0x10000;
userEntry.CommitChanges();
Console.WriteLine("Se ha cambiado la contraseña");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
But this code gets the following error in the invocation line (userEntry.Invoke("SetPassword", new object[] { newPassword });:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Runtime.InteropServices.COMException: The RPC server is unavailable. (Excepción de HRESULT: 0x800706BA)
which in english means RCP server unavailable. I've been stuck here for some days, and I've only found that it can be because of some authentication problem.
Invoking the method "Groups" works (userEntry.Invoke("Groups");) and the administrator, which is the user I'm loging in with to the ActiveDirectory has all the privileges. Also the password policy is completly permissive with no minimum lenght or complexity.
Again, because the program must iterate through, the real program actually iterates through the DirectoryEntry's children with:
foreach(DirectoryEntry child in directoryEntry.Children)
{
child.Invoke("SetPassword", new object[] { newPassword });
child.CommitChanges();
}
Thank you very much!
I don't think using directoryEntry.Children.Find("CN=" + userName) will give you much performance improvement. The sAMAccountName attribute is an indexed attribute, so the search is very fast. That's one of the fastest searches you can make.
But note that your two code blocks are not equal. Find("CN=" + userName) is trying to match userName to the name of the account: the cn attribute. But your code block with the DirectorySearcher is matching userName to the sAMAccountName attribute. The cn and sAMAccountName attributes are not necessarily the same (although they might be in your domain).
But, if you still want to use Children.Find(), I suspect that the problem might be in your Path of the DirectoryEntry. Why are you doing this?
userEntry.Path = userEntry.Path.Replace(":389", "");
Does your ADScriptService.Properties.Settings.Default.ActiveDirectoryPath have :389? It doesn't need to if it starts with LDAP:// (the default LDAP port is 389).
Your userEntry.Path should look something like (depending on your domain) LDAP://CN=user,OU=Users,DC=domain,DC=com. If it's not, then you need to fix that.
A side note: There is something you can do to speed this up a lot more than changing the search. The Properties collection uses a cache. When you access a property, it checks if it is already in the cache and, if so, uses the cache. But if the property is not in the cache, then it will ask Active Directory for every attribute that has a value. That is expensive and unnecessary if you only want to read one or two attributes (especially if you're doing it for thousands of accounts).
A way around this is to tell it to get only the attributes that you want using RefreshCache, before you access any of the Properties. Like this:
userEntry.RefreshCache(new [] { "sAMAccountName", "userAccountControl" });
Then when you access those properties, it will already have them in the cache and not reach out to AD to get anything.
Also, if you are running this in a big loop over thousands of accounts, then I suggest you put the DirectoryEntry in a using statement (or call userEntry.Dispose() when you're done with it). Usually you don't need to since garbage collection is pretty good at cleaning them up. But because you're running a big loop, the garbage collector doesn't have a chance to do any cleaning, so your process can end up taking up more and more and more memory until the loop finally stops. I have had big jobs like this take several GB of memory until I started disposing the unused DirectoryEntry objects.
Update: Actually, forget about what I said about the DirectoryEntry above. You don't actually need to use the DirectoryEntry at all. Don't use searchResult.GetDirectoryEntry(). The problem here is that you've already made a search to find the account. But now you're creating a DirectoryEntry, which is just going to make another call out to AD to get information you already have. That's probably where your main performance hit is.
Instead, use the attributes returned from your search. Now, just like DirectoryEntry.Properties, if you don't specify which attributes you want returned in the search, then it will return all of them, which you don't necessarily need. So you should set the PropertiesToLoad collection, like this:
search.PropertiesToLoad.AddRange(new [] { "sAMAccountName", "userAccountControl" });
Then, after the search, you can use this to get the sAMAccountName of the account you found:
searchResult.Properties["sAMAccountName"][0]

Querying access to a UNC path on a remote machine via WMI

I want to find out if the remote host has r/w access to a network share. To start out I wanted to see if I could query the target host's ability to query the UNC path for info, ala
var query = string.Format("select * from CIM_Directory where name = '{0}'", path);
This works fine for local files, e.g.
var path = #"c:\\Windows";
However, I can't figure out an appropriate way of querying a UNC path (e.g. \\foo\bar). The query always returns a blank set. I saw a related question about executing remote files and the solution for that one ended up being PsExec. I was hoping to ideally solve this problem entirely using WMI without having to rely on 3rd party execs, or uploading my own tool to the remote host.
Cheers
Here's a little usage sample of what I am trying to do right now (var values taken out):
using System;
using System.Linq;
using System.Management;
namespace netie
{
class Program
{
static void Main()
{
var connection = new ConnectionOptions
{
Username = "user",
Password = "pass",
Authority = "domain",
Impersonation = ImpersonationLevel.Impersonate,
EnablePrivileges = true
};
var scope = new ManagementScope("\\\\remote\\root\\CIMV2", connection);
scope.Connect();
var path = #"\\\\foo\\bar\\";
var queryString = string.Format("select * from CIM_Directory where name = '{0}'", path);
try
{
var query = new ObjectQuery(queryString);
var searcher = new ManagementObjectSearcher(scope, query);
foreach (var queryObj in searcher.Get().Cast<ManagementObject>())
{
Console.WriteLine("Number of properties: {0}", queryObj.Properties.Count);
foreach (var prop in queryObj.Properties)
{
Console.WriteLine("{0}: {1}", prop.Name, prop.Value);
}
Console.WriteLine();
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.ReadLine();
}
}
}
So it looks like this is basically impossible as WMI locks you out of network access for security reasons. Looks like your best bet is WinRM or PsExec for one-offs. You can potentially enable WinRM through WMI if that's your only path of access, but I imagine that ability can be blocked by group policies. The third option is to write your own Windows Service that will respond to requests and installing that through WMI if you have the access.
In short: the answer to my question is a No. Use WinRm, PsExec, or a custom win-service solution.
I know this is an old question, but for anyone looking to do this, the following code works. (I know that it's not WMI. Given the OP's answer I didn't even try it with WMI, but I shudder to think that people may write a service for something like this.)
if (System.IO.Directory.Exists(#"[SOME UNC PATH]"))
{
System.IO.DirectoryInfo info = new System.IO.DirectoryInfo(#"[SOME UNC PATH]");
var securityInfo = info.GetAccessControl();
var rules = securityInfo.GetAccessRules(
true,
true,
typeof(System.Security.Principal.SecurityIdentifier));
foreach (var rule in rules)
{
var fileSystemRule = rule as System.Security.AccessControl.FileSystemAccessRule;
if (ruleastype != null)
{
string user = fileSystemRule.IdentityReference.Translate(
typeof(System.Security.Principal.NTAccount)).Value;
System.Diagnostics.Debug.Print("{0} User: {1} Permissions: {2}",
fileSystemRule.AccessControlType.ToString(),
user,
fileSystemRule.FileSystemRights.ToString());
}
}
}
When run it produces the following output:
Allow User: Everyone Permissions: ReadAndExecute, Synchronize
Allow User: CREATOR OWNER Permissions: FullControl
Allow User: NT AUTHORITY\SYSTEM Permissions: FullControl
Allow User: BUILTIN\Administrators Permissions: FullControl
Allow User: BUILTIN\Users Permissions: ReadAndExecute, Synchronize

Get all folders from network

I'm kind of lost, I have a task to get all folders from a network domain,
E.g. (My Network Places/Entire Network/Microsoft Windows Network/xyNetwork).
I have to get all folders and sub-folders then get all security groups assigned to this folder and the rights granted to each security group.
The second part I have done before, however, the first part which is getting a list of all folders seems to be very complicated.
Any guides or references that might help?
Well, there is a code in another similar entry that lists all the computer names from the network... That's the first part of your requirement. For the second part I think you need to dig into System.DirectoryServices classes since there are some for permissions as well... good luck.
//Lists all available computer names on the network.
public static List<String> ListNetworkComputers()
{
var computerNames = new List<String>();
var computerSchema = "Computer";
var entries = new System.DirectoryServices.DirectoryEntry("WinNT:");
foreach (var domains in entries.Children)
{
foreach (var computer in domains.Children)
{
if (computer.SchemaClassName.ToLower().Contains(computerSchema .ToLower()))
{
computerNames.Add(computer.Name);
}
}
}
return computerNames;
}
I just printed out the values and it worked fine for me.
foreach (string lst in ListNetworkComputers())
{
Console.WriteLine("PC: " + lst);
}
(Above code taken from: Getting computer names from my network places )
What you need is to access the Win32_Share WMI from your code.
Add the reference to System.Management.dll and use the following code.
code example in VB.NET from the topic here:
http://www.pcreview.co.uk/forums/finding-share-s-directory-spec-t3064222.html
C# version of the VB.net program:
class Program
{
static void Main(string[] args)
{
var objClass = new System.Management.ManagementClass("Win32_Share");
foreach(var objShare in objClass.GetInstances())
{
Console.WriteLine(String.Format("{0} -> {1}",
objShare.Properties["Name"].Value, objShare.Properties["Path"].Value));
}
}
}
You can compare the results of the code above against the result that you get by running the following command in a windows command prompt:
C:\net share
Which will give you the Share Name (shared name given when sharing i.e. MySharedDir) and the Resource (windows path i.e. C:\myshareddir).
you can simply use GetDirectories. For example:
var folders = Directory.GetDirectories(#"\\server\share");
to get all directories (i.e. include subdirectories), use the following:
var folders = Directory.GetDirectories(#"\\server\share", "*", SearchOption.AllDirectories));

Search Active Directory for computer name(s) using user input

Hello I'm stuck trying to add a function to my Windows forms program that allows a user to type in a textbox what computer or computers they would like to search for in Active Directory. The user would input the search string in a textbox then hit a button and the computers that match that search result would appear in a separate search box. Here is my code so far.
I would also like each computer name to be on a separate line such as:
computername1
computername2
computername3
Thanks!
This is what inside the button looks like:
List<string> hosts = new List<string>();
DirectoryEntry de = new DirectoryEntry();
de.Path = "LDAP://servername";
try
{
string adser = txtAd.Text; //textbox user inputs computer to search for
DirectorySearcher ser = new DirectorySearcher(de);
ser.Filter = "(&(ObjectCategory=computer)(cn=" + adser + "))";
ser.PropertiesToLoad.Add("name");
SearchResultCollection results = ser.FindAll();
foreach (SearchResult res in results)
//"CN=SGSVG007DC"
{
string computername = res.GetDirectoryEntry().Properties["Name"].Value.ToString();
hosts.Add(computername);
//string[] temp = res.Path.Split(','); //temp[0] would contain the computer name ex: cn=computerName,..
//string adcomp = (temp[0].Substring(10));
//txtcomputers.Text = adcomp.ToString();
}
txtcomputers.Text = hosts.ToString();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
de.Dispose();//Clean up resources
}
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on the System.DirectoryServices.AccountManagement namespace
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// find a computer
ComputerPrincipal computer = ComputerPrincipal.FindByIdentity(ctx, "SomeComputerName");
if (computer != null)
{
// do something here....
}
}
If you don't need to find a single computer, but search for a whole list of computers, you can use the new PrincipalSearcher interface, which basically allows you to set up a "QBE" (query-by-example) object you're looking for, defining the search criteria, and then search for matches for those criteria.
The new S.DS.AM namespace makes it really easy to play around with users and groups in AD!

Getting users of a computer

I am trying to get the list of local users of a computer using the following code.
internal void GetUsers()
{
try
{
List<string> adUsers = new List<string>();
DirectoryEntry directoryEntry = new DirectoryEntry("WinNT://" + Environment.MachineName);
foreach (DirectoryEntry child in directoryEntry.Children)
{
if (child.SchemaClassName.Equals("User", StringComparison.OrdinalIgnoreCase))
{
adUsers.Add(child.Name);
}
}
}
catch (Exception ex)
{
//Exception
}
}
This code works fine in my computer. However, when I tested it on a few other computers, the following system users were included in the list:
ASPNET,
HelpAssistant
Could some one throw some light on how I can get rid of these system users and get only users who actually log in, ie, normal users.
Thanks,
Ram
Not an answer as such, but some suggestions that might help.
I think the problem is that those accounts aren't real system accounts, so might not be so easy to distinguish.
You could look at the WMI classes Win32_UserAccount and Win32_UserProfile and see if there are any properties in there that might indicate which user accounts are normal ones and which ones are the ones you mention. Specifically, maybe the 'SIDType' or 'AccountType' properties of Win32_UserAccount or maybe the Special property of the Win32_UserProfile class.
Might be other WMI classes that might be worth looking at as well.
Or there might be some way that you can query if a user account has the interactive logon right (which I assume those two accounts might not have normally).
Have you tried enumerating the Properties collection on DirectoryEntry?
using (DirectoryEntry dirEntry = new DirectoryEntry(strchild))
{
foreach (string strPropertyName in dirEntry.Properties.PropertyNames)
{
Console.WriteLine(strPropertyName + " " + dirEntry.Properties[strPropertyName].Value.ToString());
}
}
Other than that, you may have to do an LDAP search on Active Directory to match the UserName you have found to an ActiveDirectory user.
Have a look at this article.
http://www.codeproject.com/KB/system/everythingInAD.aspx
Have fun.
The following code will get you the local users that actually have local accessible folders.
var localDrives = Environment.GetLogicalDrives();
var localUsers = new List<string>();
var query = new SelectQuery("Win32_UserAccount") { Condition = "SIDType = 1 AND AccountType = 512" };
var searcher = new ManagementObjectSearcher(query);
foreach (ManagementObject envVar in searcher.Get())
{
foreach (string drive in localDrives)
{
var dir = Path.Combine(String.Format("{0}Users", drive), envVar["name"].ToString());
if (Directory.Exists(dir))
{
localUsers.Add(envVar["name"].ToString());
}
}
}

Categories