Copy a file to Shared Drive using Impersonate C# - 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?

Related

C# WPF Application don't write a file after publishing

First of all, the forum has given me very good ideas and solutions just by reading along.
To my current case I willnot really looking for it.
I have written a WPF application that works perfectly in debug and release mode. However, as soon as I publish you, no more.
I tried different ways to publish.
The application is provided by CD, DVD or stick.
The clickonce method was once used sometimes not.
It should be created as independently executable. And the framework is included.
My problem is that after publishing via the release build, I have no permission to write to files in Environment.CurrentDirectory or outside.
In one case, the affected file is supplied but cannot be changed. As soon as I exchange the file after installation, I can change it.
In another case, I can't create a file using the directive. The StreamWriter is used. But also creating folders using System.IO is not possible.
The application is run by an administrator. Only when the application is executed with RunAsAdmin does it run without problems.
The file is a txt file that acts as a log file. However, users without administrator rights should also be able to update the log file if necessary.
What can I do so that my application can generally generate files, regardless of the user rights?
Thank you very much.
You can check my code snippet:
private bool HasPathAccessable(string path)
{
DirectorySecurity sec = Directory.GetAccessControl(path);
WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
foreach (FileSystemAccessRule acr in sec.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)))
{
//Console.WriteLine("{0} | {1} | {2} | {3} | {4}", acr.IdentityReference.Value, acr.FileSystemRights, acr.InheritanceFlags, acr.PropagationFlags, acr.AccessControlType);
if (acr.IdentityReference.Value == currentIdentity.Name)
{
if (acr.FileSystemRights == FileSystemRights.FullControl && acr.AccessControlType == AccessControlType.Allow)
{
Console.WriteLine($"{currentIdentity.Name} accessable to {path}");
return true;
}
}
}
Console.WriteLine($"{currentIdentity.Name} inaccessable to {path}");
return false;
}
private void ChangePathAccessControlCurrentUser(string path, bool isFile)
{
WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
if (isFile)
{
var pathAccessControl = File.GetAccessControl(path);
pathAccessControl.AddAccessRule(new FileSystemAccessRule(currentIdentity.Name, FileSystemRights.FullControl, AccessControlType.Allow));
File.SetAccessControl(path, pathAccessControl);
Console.WriteLine($"AddAccessRule File : {path}");
}
else
{
var pathAccessControl = Directory.GetAccessControl(path);
pathAccessControl.AddAccessRule(new FileSystemAccessRule(currentIdentity.Name, FileSystemRights.FullControl, AccessControlType.Allow));
Directory.SetAccessControl(path, pathAccessControl);
Console.WriteLine($"AddAccessRule Directory : {path}");
}
}

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());

Securely Storing Database Password

I'd like to randomly generate an encryption key and password for an SQL Server CE database when it's created, and then save the key in some secure way that would allow the program to open a connection, but not be easily reachable by potential attackers.
I'm working on an offline WPF application that stores certain user and setting information in a local database.
My current implementation is to have one "Device Password" that the user sets up which is used as the encryption key for the generated SQL Server CE database password. The base64 encrypted database password is then saved in a simple .txt settings file. When the application starts up, the user enters the Device Password and that string is used as the decryption key for the saved password. If the resulting string is able to open a connection to the database, the password was correct and the program is opened with full access.
What I'm trying to do now is modify the system to allow multiple users with specific Username/Password credentials to open the program and access the database with varying levels of privilege. The way that I'm trying to achieve this is by handling the user authentication separately, and opening the database regardless of the credentials to load some basic application info.
Below is roughly my current implementation:
var candidateDBPwd = DecryptDatabasePassword(passwordBox.Password, Settings.Instance.EncryptedDatabasePassword);
if (candidateDBPwd.IsNullOrEmpty())
{
// User's password didn't decrypt database password.
return false;
}
if (File.Exists(Constants.DB_FILE))
{
// Normal operation: Try to open the database file to see that
// we've got the correct password.
string databaseArguments = Constants.DB_ARGS_SECURE + candidateDBPwd;
using (var conn = new SqlCeConnection(databaseArguments))
{
try
{
conn.Open();
}
catch (System.Data.SqlServerCe.SqlCeException ex)
{
// Failed to open the database: User's password must have been wrong!
return false;
}
}
I've spent the past few hours researching similar issues and am now beginning to wonder if it's possible. Consensus seems to state that storing passwords or connectionStrings in the App.config file is futile because if you encrypt the sections, you still need to store that key somewhere in code. Most of the existing SO threads on the issue seem to be several years out of date and it seems that that practice has deprecated. Is there some new respectable way to store a local database password? Or would you recommend a different approach to implementing the feature?
For you information here is the code snippet that can be used to encrypt certain sections of app.config. This is machine specific encryption and I think it is most simple and straightforward way to go.
I am using this with Click-once app, so that the config sections are encrypted during the first launch of the app. It means, that it is unecrypted on the publish server, it is downloaded also unencrypted and it is encrypted right after the installation finishes and application is started.
So using this method you have to distribute your files unencrypted and they are enrypted only after the installation is completed. I suppose it can be achieved by running this code during install, it depends on how you plan to install your app.
Also you can use UnprotectSection() to unencrypt previously encrypted section.
static void EncryptConfig()
{
// Encrypt config for ClickOnce deploys on first run
// ClickOnce deploys config into 2 dirs, so the parent dir is traversed to encrypt all
if (ApplicationDeployment.IsNetworkDeployed)
{
// Get paths
Assembly asm = Assembly.GetExecutingAssembly();
string exeName = Path.GetFileName(asm.Location);
string configName = exeName + ".config";
DirectoryInfo parentPath = Directory.GetParent(Directory.GetCurrentDirectory());
// Protect config files
foreach (DirectoryInfo dir in parentPath.GetDirectories())
{
foreach (FileInfo fil in dir.GetFiles())
{
if (fil.Name == configName)
{
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = fil.FullName;
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
ProtectSection(config, "connectionStrings");
config.Save(ConfigurationSaveMode.Modified);
}
}
}
}
}
private static void ProtectSection(Configuration config, string sectionName)
{
ConfigurationSection section = config.GetSection(sectionName);
if (section != null)
{
if (!section.SectionInformation.IsProtected)
{
section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
}
section.SectionInformation.ForceSave = true;
}
else
Tools.LogWarning("Section {1} not found in {0}.",config.FilePath, sectionName);
}
You can store it in registry editor. You mention that your system is offline wpf application .

Accessing mapped drive using OpenFileDialog when impersonating

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.

Directory.CreateDirectory access to path is denied?

I have server-client application, it's a file manager
my problem is when I go inside a folder which requires access control like system folders, it becomes to read-only, but I need to move/delete or create new folder, how can I get the permission to do that?
here's how I create a new folder at the server side
public void NewFolder(string path)
{
try
{
string name = #"\New Folder";
string current = name;
int i = 0;
while (Directory.Exists(path + current))
{
i++;
current = String.Format("{0} {1}", name, i);
}
Directory.CreateDirectory(path + current);
Explore(path); //this line is to refresh the items in the client side after creating the new folder
}
catch (Exception e)
{
sendInfo(e.Message, "error");
}
}
There are often directories on a drive that even a user with administrator privileges cannot access. A directory with a name like "HDDRecovery" is quite likely to be troublesome like this. Surely it contains sensitive data that helps the user recover from disk failure. Another directory that fits this category is "c:\system volume information", it contains restore point data.
An admin can change the permissions on folders like this. But of course that doesn't solve the real problem nor is it a wise thing to do. Your user can't and shouldn't. Be sure to write code that deals with permission problems like this, simply catch the IOExeption. Keep the user out of trouble by never showing a directory that has the Hidden or System attribute set. They are the "don't mess with me" attributes.
If you want to remove directory read-only attribute use this: http://social.msdn.microsoft.com/Forums/en/vblanguage/thread/cb75ea00-f9c1-41e5-ac8e-296c302827a4
If you want to access system folders you can run your program as local administrator.
I had a similar problem (asp.net MVC vs2017) with this code:
Directory.CreateDirectory("~/temp");
Here is my solution:
// Create path on your web server
System.IO.Directory.CreateDirectory(System.Web.HttpContext.Current.Server.MapPath("~/temp"));
I also ran into an issue similar to this, but I was able to manually navigate through Windows Explorer and create directories.
However, my web app, running in VS on my laptop, hosted through my local IIS and not the built-in IIS deal for VS, was triggering the Access Denied issue.
So when I was hitting the error in code, I drilled down to glean more data from the System.Environment object and found the user, which of course was the App Pool that my app was running under in IIS.
So I opened IIS and opened the Advanced Settings for the app pool in question and changed the Identity to run under Network Service. Click OK. "cmd -> iisreset" for good measure. Try the app again, and SUCCESS!!!!
I had the same issue when creating a directory. I used DirectorySecurity as shown below:
DirectorySecurity securityRules = new DirectorySecurity();
securityRules.AddAccessRule(new FileSystemAccessRule(#"Domain\AdminAccount1", FileSystemRights.Read, AccessControlType.Allow));
securityRules.AddAccessRule(new FileSystemAccessRule(#"Domain\YourAppAllowedGroup", FileSystemRights.FullControl, AccessControlType.Allow));
DirectoryInfo di = Directory.CreateDirectory(path + current, securityRules);
Also keep in mind about the security as explained by Hans Passant's answer.
Full details can be found on MSDN.
So the complete code:
public void NewFolder(string path)
{
try
{
string name = #"\New Folder";
string current = name;
int i = 0;
while (Directory.Exists(path + current))
{
i++;
current = String.Format("{0} {1}", name, i);
}
//Directory.CreateDirectory(path + current);
DirectorySecurity securityRules = new DirectorySecurity();
securityRules.AddAccessRule(new FileSystemAccessRule(#"Domain\AdminAccount1", FileSystemRights.Read, AccessControlType.Allow));
securityRules.AddAccessRule(new FileSystemAccessRule(#"Domain\YourAppAllowedGroup", FileSystemRights.FullControl, AccessControlType.Allow));
DirectoryInfo di = Directory.CreateDirectory(path + current, securityRules);
Explore(path); //this line is to refresh the items in the client side after creating the new folder
}
catch (Exception e)
{
sendInfo(e.Message, "error");
}
}
My suspicion is that when you are running the application in client/server mode, the server portion needs to be running as Administrator, in addition to possibly removing read-only or system flags, to be able to do what you want.
That said, I agree with #HansPassant- it sounds like what you are trying to do is ill-advised.
Solved:
Directory created on remote server using below code & setting.
Share folder and give the full permission rights also in Advance
setting in the folder.
DirectoryInfo di = Directory.CreateDirectory(#"\\191.168.01.01\Test\Test1");
Test is destination folder where to create new Test1 folder(directory)

Categories