I need to access a file from a network drive with a user who may not be in the domain.
My current code is:
private async Task GetUxVersionsFromServer()
{
string path = #$"\\{IpAddress}\...\...\...";
if(!await GetFiles(path))
{
using (UNCAccessWithCredentials unc = new UNCAccessWithCredentials())
{
bool retry = true;
do
{
(var ok, var username, var password) = _dialogService.ShowPasswordInput();
if (ok)
{
if (unc.NetUseWithCredentials(path, username, "domain", password))
{
await GetFiles(path);
retry = false;
}
}
else
{
retry = false;
}
} while (retry);
}
}
}
private async Task<bool> GetFiles(string path)
{
try
{
var zipFiles = await Task.FromResult(System.IO.Directory.GetFiles(path, "VERSION*.zip"));
Versions = new ObservableCollection<string>(zipFiles);
return true;
}
catch (IOException)
{
return false;
}
}
I use the class UNCAccessWithCredential from here
It works fine.
If the user has access to the directory, the password entry should not appear.
The only problem is that I can't test if the Windows user has access to the directory without catching an exception.
Is there a way to query if the logged in Windows user has access to a network directory or not?
Is there a way to query if the logged on Windows user is in the domain?
Plenty of ways to figure out directory permissions here: How do you check for permissions to write to a directory or file?
As for the domain membership, use this: https://learn.microsoft.com/en-us/dotnet/api/system.environment.userdomainname?view=netframework-4.8
The UserDomainName property first attempts to get the domain name component of the Windows account name for the current user. If that attempt fails, this property attempts to get the domain name associated with the user name provided by the UserName property. If that attempt fails because the host computer is not joined to a domain, then the host computer name is returned.
Finally I solved it this way:
private async Task GetUxVersionsFromServer()
{
string path = #$"\\{server}\...";
if (Environment.UserDomainName.ToLower() != "myDomain")
{
bool success = false;
bool ok;
do
{
(bool result, var username, var password) = _dialogService.ShowPasswordInput();
ok = result;
if (ok)
{
try
{
using (new NetworkConnection(path, new NetworkCredential($#"myDomain\{username}", password)))
{
success = await GetFiles(path);
}
}
catch (System.ComponentModel.Win32Exception ex)
{
success = false;
}
}
} while (!success && ok);
if(!ok)
{
int test = 0;
}
}
else
{
await GetFiles(path);
}
}
I took the class NetworkConnection from here
Related
I am trying to learn how to use InstaSharper but I've got a problem. Everytime when I run my code to see the new code working, I have to login, but if I do this too many times, I will be blocked by Instagram. I was blocked on two accounts allready. So I need a solution not to always have to login like a token or something.
My login:
public static async void Login()
{
api = InstaApiBuilder.CreateBuilder()
.SetUser(user)
.UseLogger(new DebugLogger(LogLevel.Exceptions))
.SetRequestDelay(RequestDelay.FromSeconds(5, 10))
.Build();
var loginRequest = await api.LoginAsync();
if (loginRequest.Succeeded)
{
Console.WriteLine("Login In Success!");
Follow("artofbokeh");
}
else
{
Console.WriteLine("Login Failed");
}
}
Yes you can save your state in a a file and load it again.
Please give this piece of code a try, it is taken form the official github repo which can be found under this link: Github repo
// create user session data and provide login details
var userSession = new UserSessionData
{
UserName = "username",
Password = "password"
};
// create new InstaApi instance using Builder
_instaApi = InstaApiBuilder.CreateBuilder()
.SetUser(userSession)
.UseLogger(new DebugLogger(LogLevel.Exceptions)) // use logger for requests and debug messages
.SetRequestDelay(TimeSpan.FromSeconds(2))
.Build();
const string stateFile = "state.bin";
try
{
if (File.Exists(stateFile))
{
Console.WriteLine("Loading state from file");
Stream fs = File.OpenRead(stateFile);
fs.Seek(0, SeekOrigin.Begin);
_instaApi.LoadStateDataFromStream(fs);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
if (!_instaApi.IsUserAuthenticated)
{
// login
Console.WriteLine($"Logging in as {userSession.UserName}");
var logInResult = await _instaApi.LoginAsync();
if (!logInResult.Succeeded)
{
Console.WriteLine($"Unable to login: {logInResult.Info.Message}");
return false;
}
}
var state = _instaApi.GetStateDataAsStream();
using (var fileStream = File.Create(stateFile))
{
state.Seek(0, SeekOrigin.Begin);
state.CopyTo(fileStream);
}
Edit the function to get the state data does the following:
public Stream GetStateDataAsStream()
{
var state = new StateData
{
DeviceInfo = _deviceInfo,
IsAuthenticated = IsUserAuthenticated,
UserSession = _user,
Cookies = _httpRequestProcessor.HttpHandler.CookieContainer
};
return SerializationHelper.SerializeToStream(state);
}
So yes you save all needed information to not login everytime. If this doesnt work for you you are doing something wrong. Please post your code where you load / save the state file.
I'm writing some kind of a mini AD tool (with VS-C#) to our organization and got into an issue.
I have a main function that searches the user (when I click on it in a listview) and some functions that manipulate the user's object.
public DirectoryEntry GetUser(string username)
{
try
{
Forest currentForest = Forest.GetCurrentForest();
GlobalCatalog gc = currentForest.FindGlobalCatalog();
using (DirectorySearcher searcher = gc.GetDirectorySearcher())
{
searcher.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=" + username + "*))";
SearchResult results = searcher.FindOne();
if (!(results == null))
{
DirectoryEntry de = new DirectoryEntry(results.Path, strAdminUser, strAdminPass, AuthenticationTypes.Secure);
de.RefreshCache(new string[] { "canonicalName" });
de.Path = de.Properties["canonicalName"].Value.ToString();
de.CommitChanges();
return de;
}
else
{
return null;
}
}
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return null;
}
}
and here's an example of a function that checks if the user is locked:
public bool IsUserLocked(string username)
{
try
{
DirectoryEntry de = GetUser(username);
string attName = "msDS-User-Account-Control-Computed";
de.RefreshCache(new string[] { attName });
const int UF_LOCKOUT = 0x0010;
int userFlags = /*(int)*/Convert.ToInt32(de.Properties[attName].Value);
if ((userFlags & UF_LOCKOUT) == UF_LOCKOUT)
{
return true;
}
de.Dispose();
return false;
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return false;
}
}
The function that checks the locked status of a user always fails with an error: "Unspecified error", but if I'm not changing the Directory Entry's path in the first function I get "The server is unwilling to process the request" error (I'm using proper service username and password with all the permissions needed) but still it happens.
Can someone spot the issue?
How about using System.DirectoryServices.AccountManagement namespace? If you have no issue using the new namespace, there's a simpler way to check if user account is locked and unlock if needed.
public bool IsUserLocked (string username)
{
using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "yourdomain.com")
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx, username)
{
if (user != null) return user.IsAccountLockedOut();
}
}
return null;
}
And similarly, you can unlock the user account if needed.
...
if (user != null)
{
user.UnlockAccount();
user.Save();
}
Got it...
This has solved my issue:
de.Path = results.Path.Replace("GC://DCNAME.", "LDAP://");
Since I'm searcing on the Global Catalog, I had to replace a portion in the path to match it to the correct path:
public DirectoryEntry GetUser(string username)
{
try
{
Forest currentForest = Forest.GetCurrentForest();
GlobalCatalog gc = currentForest.FindGlobalCatalog();
using (DirectorySearcher searcher = gc.GetDirectorySearcher())
{
searcher.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=" + username + "*))";
SearchResult results = searcher.FindOne();
if (!(results == null))
{
DirectoryEntry de = new DirectoryEntry(results.Path, strAdminUser, strAdminPass, AuthenticationTypes.Secure);
de = new DirectoryEntry(results.Path);
de.Path = results.Path.Replace("GC://DCNAME.", "LDAP://");
de.CommitChanges();
//System.Windows.Forms.MessageBox.Show(de.Path);
return de;
}
else
{
return null;
}
}
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return null;
}
}
Now the path returns to the function called GetUser in the correct format :)
I am trying retrieve the full name of the user, that is currently logged in.
It usually works fine with this small snippet, I found:
[Export(typeof(IAdManager))]
public class AdManager : IAdManager
{
public string GetFullName()
{
try
{
DirectoryEntry de = new DirectoryEntry("WinNT://" + Environment.UserDomainName + "/" + Environment.UserName);
return de.Properties["fullName"].Value.ToString();
}
catch
{
return string.Empty;
}
}
}
Unfortunately it stops working, when I am trying to start the application on Windows start. The result is an empty (or null?) string.
I've put a shortcut in here:
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
Any ideas, why exactly it's not working? Is it because that Location actually loads up the applications before the user logs in?
Try getting the FullName directly from AD.
public static string GetFullName()
{
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "yourdomain.com"))
{
string username = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx, username))
{
if (user != null)
{
return user.DisplayName;
}
}
}
return null;
}
I write a program, where the user can select local and remote directories. Before performing any operation on a selected path, I want to check if it's valid and if the user has read/write permission(s) granted on it. That's what I have:
private bool checkInput(string dirPath, string depthStr, string filename)
{
...
string reason = null;
try
{
...
else if ((reason = CAN_ACCESS(dirPath)) == null)
{
if (!(new DirectoryInfo(dirPath)).Exists)
reason = string.Format("The directory '{0}' is no directory or does not exist!", dirPath);
...
}
}
catch (Exception ex)
{
reason = ex.Message;
}
if (reason != null)
{
MessageBox.Show(reason, "Wrong input", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
return true;
}
public static string CAN_ACCESS(string dirPath)
{
Uri uri = new Uri(dirPath);
//UserFileAccessRights rights = new UserFileAccessRights(dirPath);
string errorMsg = null;
if (uri.IsUnc && !haveAccessPermissionOnHost(uri))
{
string domain = new Uri(dirPath).Host;
domain = dirPath;
ErrorClass res = PinvokeWindowsNetworking.connectToRemote(domain, null, null, true);
if (res == null)
return null;
else
errorMsg = string.Format("(Code {0}): {1}", res.num, res.message);
}
return errorMsg;
}
private static bool haveAccessPermissionOnHost(Uri uri)
{
throw new NotImplementedException();
}
The primary question is, how to simple check in haveAccessPermissionOnHost(Uri uri), if a user has read/write permission(s) on a remote host and unc path granted.
Hint:
If no permissions granted, the well known Windows logon window pops up to force the user to enter some credentials, which is working fine:
ErrorClass res = PinvokeWindowsNetworking.connectToRemote(domain, null, null, true);
I got it from this question (and changed the provided code to return the ErrorClass, null on success).
My secondary question is, if my structure in general is the way to go to check a path, whether it's local or remote, for validity (that's why I provided more code as necessary).
I found myself a workaround, since nobody replied to this question:
public static bool canAccess(DirectoryInfo dirInfo)
{
Uri uri = new Uri(dirInfo.FullName);
// is this a remote path?
if (uri.IsUnc)
{
if (hostOnline(uri.Host))
{
// check if remote path exists
if (!dirInfo.Exists)
{
string domain = dirInfo.FullName;
ErrorClass res = PinvokeWindowsNetworking.connectToRemote(domain, null, null, true);
if (res == null)
{
if (!dirInfo.Exists)
throw new Exception(
string.Format("No access permissions or directory not existing."));
return true;
}
else if (res.num == 53)
throw new Exception("Remote directory not existing.");
else
throw new Exception(
string.Format("(Code {0}): {1}", res.num, res.message));
}
}
else
throw new Exception("Unknown host or not online.");
}
// local directory existing?
return dirInfo.Exists;
}
private static bool hostOnline(string hostname)
{
Ping ping = new Ping();
try
{
PingReply pr = ping.Send(hostname);
return pr.Status == IPStatus.Success;
}
catch (Exception)
{
return false;
}
}
I have the code:
public bool RemoveUserFromAdministratorsGroup(UserPrincipal oUserPrincipal, string computer)
{
try
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Machine, computer, null, ContextOptions.Negotiate, _sServiceUser, _sServicePassword);
GroupPrincipal oGroupPrincipal = GroupPrincipal.FindByIdentity(oPrincipalContext, "Administrators");
oGroupPrincipal.Members.Remove(oUserPrincipal);
oGroupPrincipal.Save();
return true;
}
catch
{
return false;
}
}
It is worked without any excaption. But when i run my app again i see this user in my listview. So, the user wasn't removed.
I have solved the issue without AccountManagment namespace.
public bool RemoveUserFromAdminGroup(string computerName, string user)
{
try
{
var de = new DirectoryEntry("WinNT://" + computerName);
var objGroup = de.Children.Find(Settings.AdministratorsGroup, "group");
foreach (object member in (IEnumerable)objGroup.Invoke("Members"))
{
using (var memberEntry = new DirectoryEntry(member))
if (memberEntry.Name == user)
objGroup.Invoke("Remove", new[] {memberEntry.Path});
}
objGroup.CommitChanges();
objGroup.Dispose();
return true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
return false;
}
}
The below solution is for deleting the user with the help of Directory Service ...
using System.DirectoryServices
private DeleteUserFromActiveDirectory(DataRow in_Gebruiker)
{
DirectoryEntry AD = new DirectoryEntry(strPathActiveDirectory ,
strUsername, strPassword)
DirectoryEntry NewUser =
AD.Children.Find("CN=TheUserName", "User");
AD.Children.Remove(NewUser);
AD.CommitChanges();
AD.Close();
}
I don't know what is exactly your problem but coding this way :
try
{
PrincipalContext context = new PrincipalContext(ContextType.Domain, "WM2008R2ENT:389", "dc=dom,dc=fr", "jpb", "passwd");
/* Retreive a user principal
*/
UserPrincipal user = UserPrincipal.FindByIdentity(context, "user1");
/* Retreive a group principal
*/
GroupPrincipal adminGroup = GroupPrincipal.FindByIdentity(context, #"dom\Administrateurs");
foreach (Principal p in adminGroup.Members)
{
Console.WriteLine(p.Name);
}
adminGroup.Members.Remove(user);
adminGroup.Save();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Give me the following exception :
Information about the domain could not be retrieved (1355)
Digging a bit arround that show me that I was running my code on a computer that was not on the target domain. When I run the same code from the server itself it works. It seems that the machine running this code must at least contact the DNS of the target domain.