Accessing mapped drive using OpenFileDialog when impersonating - c#

The user needs to login with his windows credentials at startup of our application. These credentials are used to impersonate the user and run the main form under the provided login. We do now have an OpenFileDialog where the user can select files.
The problem arises now when the user accesses a mapped network drive (the ones are shown from the user logged on to the machine and not my program somehow). When pressing the OK button, the OpenFileDialog displays an error message (path cannot be found/accessed. make sure it exists).
As I have seen in other posts, it would be possible to map these path back to UNC path, but the dialog doesn't even return so I could do this. Is there some workaround other than making my own open file dialog?
Impersonation part:
bool success = NativeMethods.LogonUser(userName, domain, password, (int)LogonType.Logon32LogonNewCredentials, (int)LogonProvider.Logon32ProviderWinnt50, ref pExistingTokenHandle);
if (success)
{
success = NativeMethods.DuplicateToken(pExistingTokenHandle, (int)SecurityImpersonationLevel.SecurityImpersonation, ref pDuplicateTokenHandle);
if (success)
{
// Return the impersonation context
WindowsIdentity identity = new WindowsIdentity(pDuplicateTokenHandle);
impersonationContext = identity.Impersonate();
return impersonationContext;
}
}
Open dialog part
OpenFileDialog openFileDialog = new OpenFileDialog
{
Multiselect = true,
InitialDirectory = Environment.CurrentDirectory,
Title = "Select file"
};
bool? dialogResult = openFileDialog.ShowDialog(this);
if (dialogResult.Value)
{
openFileDialog.FileNames.ToList().ForEach(t => MessageBox.Show("File: " + t));
}

Undoing impersonation before showing the dialog has solved the issue of selecting files on a network drive. The question itself is still valid as also service accounts may need to access network drives.

Related

Copy a file to Shared Drive using Impersonate C#

I want to copy a file to a Shared folder. I can manually copy the file but I want to schedule it to run every hour.
First I made a batch file:
xcopy "D:\DATA\file.bak" "\\SharedFolder\DATA\"
It shows that the file has been copied, but there is nothing in the Shared folder.
Next I tried to use File.Copy but it didn't work as expected. I find that I need to have a User Identity for this.
So, here is my code:
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
IntPtr user = IntPtr.Zero;
bool loggedon = LogonUser(
"User", //Username
"Domain", //Domain
"", // Password
2, // interactive
0, // default
out user);
if (loggedon)
{
using (WindowsIdentity.Impersonate(user))
{
File.Copy(Sourcepath, DestPath, true);
MessageBox.Show("Done");
}
}
else
{
MessageBox.Show("Failed");
}
But this account doesn't have a password so I don't know what to put in the password field.
Is there another way to impersonate the current logged in user?
Or do I need to make a password?

C# Folder browse Dialog not showing Network shared folders win10

I have created an application(windows) compiled with .NET 4.6.1 and used the FolderBrowserDialog object. When a button is pressed I execute this code:
FolderBrowserDialog folderbrowserdialog = new FolderBrowserDialog();
folderbrowserdialog.Description = "Custom Description";
if (folderbrowserdialog.ShowDialog() == DialogResult.OK)
{
filePath = folderbrowserdialog.SelectedPath ;
}
what i get from the folderbrowserdialog(like foto)
however ,the folder browserdialog is not showing the networks shared folder(that the purpose of my app) otherewise just the pc folders.
but what i want to get it is the network shared folders which could i also access from windows 10 like foto here:
notes to be marked:
i could not use the open file dialog cause i need the folder location.
i desgined the Appto be opened just like admin by adding manisfest so the app is always starting like admin.
the app should be comptiable with windows 10,7
note i know that i could try setting this registry option (could be broken in Win10):
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Policies/System
EnableLinkedConnections=1
but it does not make a sense to add this registry by every customer PC
so is there any tipps to show the network shared folders in FolderBrowserDialog ?
Finally after reading many topics i found that the only solution is to add a Registry key programmatically so here how to add specfic C# Registry Subkey with dword value:
i wrote a method wich could all use it
just to let you know after using it you have to restart the device after it ,it will work ;)
public void ConfigureWindowsRegistry()
{
RegistryKey localMachine = RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, RegistryView.Registry64); //here you specify where exactly you want your entry
var reg = localMachine.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", true);
if (reg == null)
{
reg = localMachine.CreateSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", true);
}
if (reg.GetValue("EnableLinkedConnections") == null)
{
reg.SetValue("EnableLinkedConnections", "1", RegistryValueKind.DWord);
MessageBox.Show(
"Your configuration is now created,you have to restart your device to let app work perfektly");
}
}
I had the same issue. The reason of the problem: I was using as an Administrator. The mapped drives are related to the user, so I tried to use as an normal user and I could see the mapped drives.

how to modify hosts file using c# with an admin rights and without manual intervention

I've been trying with number of ways but I'm unable to avoid the alert which says 'Do you want to open the application as administrator'. can some one suggest such piece of code which avoids/handles the alert to add new entry into hosts file.
Thanks in advance..
public bool ModifyHostsFile(string sEntryIPAddr, string sEntryURL)
{
try
{
WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
bool administrativeMode = principal.IsInRole(WindowsBuiltInRole.Administrator);
if (!administrativeMode)
{
//ProcessStartInfo startInfo = new ProcessStartInfo();
//startInfo.Verb = "runas";
//startInfo.FileName = Application.ExecutablePath;
//Process.Start(startInfo);
//bool bStatus = GrantAccess(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), #"drivers\etc\hosts"));
using (StreamWriter w = File.AppendText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), #"drivers\etc\hosts")))
{
w.WriteLine(sEntryIPAddr + " " + sEntryURL);
}
Application.Exit();
}
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
private bool GrantAccess(string fullPath)
{
DirectoryInfo dInfo = new DirectoryInfo(fullPath);
DirectorySecurity dSecurity = dInfo.GetAccessControl();
dSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.NoPropagateInherit, AccessControlType.Allow));
dInfo.SetAccessControl(dSecurity);
return true;
}
No, you definitely need admin access to modify the file - Or else any virus could hijack the hosts file and redirect all browsers requests to a malicious site.
I don't have the rep to comment to ask some question I have but instead I will answer based on assumptions.
Your goal
To run an application with administrative rights
Why the UAC popup?
The popup you get asking for Admin Rights is to prevent applications from simply taking admin rights without your knowledge and thereby modifying your system's critical files
How to prevent it?
I HIGHLY recommend not taking this step but the first option that comes to mind is to disable UAC (User Account Controls) from your control panel
Alternative
An alternative could be to run the program as a scheduled task, set to run with the highest privileges. Then you can execute the program by running the scheduled task and have admin access without the UAC popup.
This can be done via command-line or via the GUI and still requires admin privileges for the creation of the schedules task

Editing registry value for newly created user

I have a .NET application that creates a new local user like so:
var principalContext = new PrincipalContext(ContextType.Machine);
var userPrincipal = new UserPrincipal(principalContext);
userPrincipal.Name = StandardUserName.Text;
userPrincipal.Description = "New local user";
userPrincipal.UserCannotChangePassword = true;
userPrincipal.PasswordNeverExpires = true;
userPrincipal.Save();
// Add user to the users group
var usersGroupPrincipal = GroupPrincipal.FindByIdentity(principalContext, UserGroupName.Text);
usersGroupPrincipal.Members.Add(userPrincipal);
usersGroupPrincipal.Save();
Next, I want to set some registry values for that user. For that, I need the user's SID:
private string GetSidForStandardUser()
{
var principalContext = new PrincipalContext(ContextType.Machine);
var standardUser = UserPrincipal.FindByIdentity(principalContext, StandardUserName.Text);
return standardUser.Sid.ToString();
}
And create a new subkey:
var key = string.Format("{0}{1}", GetSidForStandardUser(), keyString);
var subKey = Registry.Users.CreateSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree);
However, I get an IOException on the call to CreateSubKey that tells me the Parameter is invalid. This happens because the subkey for that user does not exist yet until the user logs in for the first time. If I check regedit (under admin privileges) before logging in as the new user I can see that the SID does not exist under HKEY_Users. If I log in as the new user, then log out and back in as my original user and refresh regedit, the new SID exists.
My question is: is there a way to add subkeys for users that haven't logged in yet? I'd like to avoid having to log in as the new user and then back out halfway during the process.
I've since found a solution to the problem, but it's not pretty and it raises all sorts of new problems you have to deal with. Still, it works. I'm posting the solution here for my own reference and for others who may have need for it in the future.
The problem is that a user's registry hive is in their user profile folder (e.g. c:\users\username) in a file called NTUSER.DAT. However, a user's user profile folder isn't created until they log in, so when you create a new user there's no user profile yet and no NTUSER.DAT file containing their registry hive, so you can't edit any of their registry settings.
There's a trick, though: the user profile does get created when you run something under that user's credentials. There's an executable called runas.exe that lets you run a second executable under a specified user's credentials. If you create a new user and make it run, say, cmd.exe, like so:
runas /user:newuser cmd.exe
...it'll open a Cmd instance, but more importantly, create newuser's profile in the \users folder, including NTUSER.DAT.
Now, Cmd.exe leaves a command window open, which you can close manually but it's kind of clunky. https://superuser.com/a/389288 pointed me to rundll32.exe which, when run without any parameters, does nothing and exits immediately. Also, it's available on every Windows installation.
So, by calling runas and telling it to run rundll32.exe as the new user, we can create the user's profile without any further interaction:
Process.Start("runas", string.Format("/user:{0} rundll32", "newuser"));
Well... almost with no interaction. Runas opens a console window that requires you to enter the user's password, even if no password is set (it wants you to just press enter). This is annoying, but can be solved with some clever use of Pinvoke and optionally System.Windows.Forms to bring the window to the foreground and send it some keypresses:
var createProfileProcess = Process.Start("runas",
string.Format("/user:{0} rundll32",
"newuser"));
IntPtr hWnd;
do
{
createProfileProcess.Refresh();
hWnd = createProfileProcess.MainWindowHandle;
} while (hWnd.ToInt32() == 0);
SetForegroundWindow(hWnd);
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
This creates the profile, waits until the window has a handle, and then calls the Win32 function SetForegroundWindow() to bring it to the foreground. Then, it uses SendKeys.SendWait to send an enter key to that window. If you don't want to use a WinForms DLL, there are Win32 functions you can PInvoke for this, but for this particular scenario I found the winforms way quicker and easier.
This works, but reveals yet another problem: runas won't let you run stuff under an account that has no password. Superuser to the rescue again; https://superuser.com/a/470539 points out that there's a Local Policy called Limit local account use of blank passwords to console logon only that can be disabled to allow this exact scenario. I didn't want users to have to manually disable this policy, so I used the corresponding registry value LimitBlankPasswordUse in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa.
I now disable the policy by setting the registry value to 0, run the runas command to create the profile, then re-enable the policy by setting the value to 1 afterwards.(It would probably be cleaner to check the value first and only re-enable it if it was set in the first place, but for demonstration purposes this will do:
const string keyName = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Lsa";
Registry.SetValue(keyName, "LimitBlankPasswordUse", 0);
var createProfileProcess = Process.Start("runas",
string.Format("/user:{0} rundll32",
"newuser"));
IntPtr hWnd;
do
{
createProfileProcess.Refresh();
hWnd = createProfileProcess.MainWindowHandle;
} while (hWnd.ToInt32() == 0);
SetForegroundWindow(hWnd);
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
Registry.SetValue(keyName, "LimitBlankPasswordUse ", "1");
This works! However, the user's registry hive isn't loaded yet, so you still won't be able to read or write to it. For that, the process needs a couple of privileges, which you can again provide using some Win32 methods:
OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
out _myToken);
LookupPrivilegeValue(null, SE_RESTORE_NAME, out _restoreLuid);
LookupPrivilegeValue(null, SE_BACKUP_NAME, out _backupLuid);
_tokenPrivileges.Attr = SE_PRIVILEGE_ENABLED;
_tokenPrivileges.Luid = _restoreLuid;
_tokenPrivileges.Count = 1;
_tokenPrivileges2.Attr = SE_PRIVILEGE_ENABLED;
_tokenPrivileges2.Luid = _backupLuid;
_tokenPrivileges2.Count = 1;
AdjustTokenPrivileges(_myToken,
false,
ref _tokenPrivileges,
0,
IntPtr.Zero,
IntPtr.Zero);
AdjustTokenPrivileges(_myToken,
false,
ref _tokenPrivileges2,
0,
IntPtr.Zero,
IntPtr.Zero);
And finally load the hive using the new user's SID:
// Load the hive
var principalContext = new PrincipalContext(ContextType.Machine);
var standardUser = UserPrincipal.FindByIdentity(principalContext, "newuser");
var sid = standardUser.Sid.ToString();
StringBuilder path = new StringBuilder(4 * 1024);
int size = path.Capacity;
GetProfilesDirectory(path, ref size);
var filename = Path.Combine(path.ToString(), "newuser", "NTUSER.DAT");
Thread.Sleep(2000);
int retVal = RegLoadKey(HKEY_USERS, sid, filename);
I found most of this code in Load registry hive from C# fails.
RegLoadKey should return 0 on success. I noted that occasionally, it would fail to load the hive for no apparent reason. Reasoning that perhaps the necessary files in the user profile had not yet been created, I added a Thread.Sleep(2000) before loading the hive to give Windows time to create all the necessary files. There's probably a neater way to do this, but for now this'll work.
Now, you can load and set registry values for newuser using the newuser's SID, for instance:
var subKeyString = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced";
var keyString = string.Format("{0}{1}", sid, subKeyString);
var subKey = Registry.Users.CreateSubKey(keyString,
RegistryKeyPermissionCheck.ReadWriteSubTree);
subKey.SetValue("EnableBalloonTips", 0, RegistryValueKind.DWord);
Just to be sure, I also unloaded the registry hive when I was done. I'm not sure if it's required, but it seems like the neat thing to do:
var retVal = RegUnLoadKey(HKEY_USERS, GetSidForStandardUser());

Can't log to Skype when I use Process.Start

The problem that I have is that when I try to open Skype using Process.start in C#, the Skype window opens but when I put the user and password, I can't log on. But when i go directly to "C:\Program Files (x86)\Skype\Phone\Skype.exe" and I choose "Run as different user" (Shift + right clic),the Skype window opens, I put the credentials and in that case it works.
In Windows I have logged in, as local user and the user I need to run Skype is a Domain user. The connection to skype is through proxy.
I have checked in task manager, and the process starts with the correct user but somehow the behavior is different when I do it right clicking the folder and from the Process.start.
Here is my code:
ProcessStartInfo oPi = new ProcessStartInfo();
oPi.Domain = "Domain";
oPi.UserName = "User";
oPi.Password = oSecureString;
oPi.FileName = "C:\\Program Files (x86)\\Skype\\Phone\\Skype.exe";
oPi.UseShellExecute = false;
Process oP = Process.Start(oPi);
Just put the code in the button's click event handler and use using System.Diagnostics as namespace to get Process:
protected void btn_Click(object sender, EventArgs e)
{
try
{
Process.Start("C:\\Program Files (x86)\\Skype\\Phone\\Skype.exe");
}
catch (Exception ex)
{
Response.Write("" + ex);
}
}

Categories