How to copy ntfs permissions - c#

I found a method for copying ntfs permissions information from one existing folder to a newly created one - I'm not sure if it's doing the work it should do. Maybe one can have a look at the method and give some comments:
private static void CopySecurityInformation(String source, String dest)
{
FileSecurity fileSecurity = File.GetAccessControl(
source,
AccessControlSections.All);
FileAttributes fileAttributes = File.GetAttributes(source);
File.SetAccessControl(dest, fileSecurity);
File.SetAttributes(dest, fileAttributes);
}
Thanks for your help,
Daniel

I tried following the OP suggested pattern for copying a file's ACLs and attributes and found a few issues in our application. Hopefully this information helps others.
According to MSDN, using the File.SetAccessControl() method as shown above will not work.
The SetAccessControl method persists only FileSecurity objects that
have been modified after object creation. If a FileSecurity object
has not been modified, it will not be persisted to a file. Therefore,
it is not possible to retrieve a FileSecurity object from one file and
reapply the same object to another file.
It is necessary to make a new FileSecurity object for the destination file and assign to this a copy of the source FileSecurity object.
Here is an example of a pattern that works from our app.
public static void CopyFile(string source, string destination)
{
// Copy the file
File.Copy(source, destination, true);
// Get the source file's ACLs
FileSecurity fileSecuritySource = File.GetAccessControl(source, AccessControlSections.All);
string sddlSource = fileSecuritySource.GetSecurityDescriptorSddlForm(AccessControlSections.All);
// Create the destination file's ACLs
FileSecurity fileSecurityDestination = new FileSecurity();
fileSecurityDestination.SetSecurityDescriptorSddlForm(sddlSource);
// Set the destination file's ACLs
File.SetAccessControl(destination, fileSecurityDestination);
// copy the file attributes now
FileAttributes fileAttributes = File.GetAttributes(source);
File.SetAttributes(destination, fileAttributes);
}
After fixing the first issue, then we found the owner attribute was not being copied. Instead, the destination file was owned by Administrator in our case. To get around that the process needs to enable the SE_RESTORE_NAME privilege. Check out AdjustTokenPrivileges on pinvoke.net for a complete Privileges class that makes setting privileges easy.
The last thing that we had to deal with was UAC. We needed to restart our app with administrative rights if the user is running under UAC. Here is another example from our app that deals with relaunching the app with elevated privileges.
private static void RelaunchWithAdministratorRights(string[] args)
{
// Launch as administrator
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.UseShellExecute = true;
processStartInfo.WorkingDirectory = Environment.CurrentDirectory;
string executablePath = System.Reflection.Assembly.GetExecutingAssembly().Location;
processStartInfo.FileName = executablePath;
processStartInfo.Verb = "runas";
if (args != null && args.Count() > 0)
{
string arguments = args[0];
for (int i = 1; i < args.Count(); i++)
arguments += " " + args[i];
processStartInfo.Arguments = arguments;
}
try
{
using (Process exeProcess = Process.Start(processStartInfo))
{
exeProcess.WaitForExit();
}
}
catch
{
// The user refused to allow privileges elevation. Do nothing and return directly ...
}
Environment.Exit(0);
}
Ours was a console app so we need to pass the args parameter from the Main method to the RelaunchWithAdministratorRights(args). Non-console apps could pass null instead. We call RelaunchWithAdministratorRights from within catch blocks as in:
try
{
...
}
catch (SecurityException)
{
RelaunchWithAdministratorRights(args);
return;
}
In our app we just return after the call to RelaunchWithAdministratorRights to exit the instance of the app that lacked privileges. Depending on your app, you may prefer to throw your way out. Regardless, after returning from RelaunchWithAdministratorRights you don't want this instance to continue processing.
Enjoy!

It does slightly more than just copying the NTFS permissions. It also copies the attributes of the file. I'm not quite certain whether it copies inherited permissions, though, but run it once and you should be able to find out.
Note that copying permissions on and of itself requires special permissions (administrators have these, of course), be sure the process running this method has the required permissions to query, view and set permissions on those objects.

Related

How do I modify directory timestamps when File Explorer is open?

My application creates files and directories throughout the year and needs to access the timestamps of those directories to determine if it's time to create another one. So it's vital that when I move a directory I preserve its timestamps. I can do it like this when Directory.Move() isn't an option (e.g. when moving to a different drive).
FileSystem.CopyDirectory(sourcePath, targetPath, overwrite);
Directory.SetCreationTimeUtc (targetPath, Directory.GetCreationTimeUtc (sourcePath));
Directory.SetLastAccessTimeUtc(targetPath, Directory.GetLastAccessTimeUtc(sourcePath));
Directory.SetLastWriteTimeUtc (targetPath, Directory.GetLastWriteTimeUtc (sourcePath));
Directory.Delete(sourcePath, true);
However, all three of these "Directory.Set" methods fail if File Explorer is open, and it seems that it doesn't even matter whether the directory in question is currently visible in File Explorer or not (EDIT: I suspect this has something to do with Quick Access, but the reason isn't particularly important). It throws an IOException that says "The process cannot access the file 'C:\MyFolder' because it is being used by another process."
How should I handle this? Is there an alternative way to modify a timestamp that doesn't throw an error when File Explorer is open? Should I automatically close File Explorer? Or if my application simply needs to fail, then I'd like to fail before any file operations take place. Is there a way to determine ahead of time if Directory.SetCreationTimeUtc() for example will encounter an IOException?
Thanks in advance.
EDIT: I've made a discovery. Here's some sample code you can use to try recreating the problem:
using System;
using System.IO;
namespace CreationTimeTest
{
class Program
{
static void Main( string[] args )
{
try
{
DirectoryInfo di = new DirectoryInfo( #"C:\Test" );
di.CreationTimeUtc = DateTime.UtcNow;
Console.WriteLine( di.FullName + " creation time set to " + di.CreationTimeUtc );
}
catch ( Exception ex )
{
Console.WriteLine( ex );
//throw;
}
finally
{
Console.ReadKey( true );
}
}
}
}
Create C:\Test, build CreationTimeTest.exe, and run it.
I've found that the "used by another process" error doesn't always occur just because File Explorer is open. It occurs if the folder C:\Test had been visible because C:\ was expanded. This means the time stamp can be set just fine if File Explorer is open and C:\ was never expanded. However, once C:\Test becomes visible in File Explorer, it seems to remember that folder and not allow any time stamp modification even after C:\ is collapsed. Can anyone recreate this?
EDIT: I'm now thinking that this is a File Explorer bug.
I have recreated this behavior using CreationTimeTest on multiple Windows 10 devices. There are two ways an attempt to set the creation time can throw the "used by another process" exception. The first is to have C:\Test open in the main pane, but in that case you can navigate away from C:\Test and then the program will run successfully again. But the second way is to have C:\Test visible in the navigation pane, i.e. to have C:\ expanded. And once you've done that, it seems File Explorer keeps a handle open because the program continues to fail even once you collapse C:\ until you close File Explorer.
I was mistaken earlier. Having C:\Test be visible doesn't cause the problem. C:\Test can be visible in the main pane without issue. Its visibility in the navigation pane is what matters.
Try this:
string sourcePath = "";
string targetPath = "";
DirectoryInfo sourceDirectoryInfo = new DirectoryInfo(sourcePath);
FileSystem.CopyDirectory(sourcePath, targetPath, overwrite);
DirectoryInfo targetDirectory = new DirectoryInfo(targetPath);
targetDirectory.CreationTimeUtc = sourceDirectoryInfo.CreationTimeUtc;
targetDirectory.LastAccessTimeUtc = sourceDirectoryInfo.LastAccessTimeUtc;
targetDirectory.LastWriteTimeUtc = sourceDirectoryInfo.LastWriteTimeUtc;
Directory.Delete(sourcePath, true);
This will allow you to set the creation/access/write times for the target directory, so long as the directory itself is not open in explorer (I am assuming it won't be, as it has only just been created).
I am suspecting FileSystem.CopyDirectory ties into Explorer and somehow blocks the directory. Try copying all the files and directories using standard C# methods, like this:
DirectoryCopy(#"C:\SourceDirectory", #"D:\DestinationDirectory", true);
Using these utility methods:
private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
{
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
if (!dir.Exists)
{
throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceDirName);
}
if ((dir.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
// Don't copy symbolic links
return;
}
var createdDirectory = false;
// If the destination directory doesn't exist, create it.
if (!Directory.Exists(destDirName))
{
var newdir = Directory.CreateDirectory(destDirName);
createdDirectory = true;
}
// Get the files in the directory and copy them to the new location.
DirectoryInfo[] dirs = dir.GetDirectories();
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
if ((file.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
continue; // Don't copy symbolic links
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, false);
CopyMetaData(file, new FileInfo(temppath));
}
// If copying subdirectories, copy them and their contents to new location.
if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
DirectoryCopy(subdir.FullName, temppath, copySubDirs);
}
}
if (createdDirectory)
{
// We must set it AFTER copying all files in the directory - otherwise the timestamp gets updated to Now.
CopyMetaData(dir, new DirectoryInfo(destDirName));
}
}
private static void CopyMetaData(FileSystemInfo source, FileSystemInfo dest)
{
dest.Attributes = source.Attributes;
dest.CreationTimeUtc = source.CreationTimeUtc;
dest.LastAccessTimeUtc = source.LastAccessTimeUtc;
dest.LastWriteTimeUtc = source.LastWriteTimeUtc;
}

How to launch an executable in C# with specific guidelines

Currently I'm porting a c++ exe launch to c#. I'm able to read through and understand the c++ code, but I'm struggling to find the c# equivalent. I believe that the original code launches the exe by utilizing the command prompt.
I think it would be best to display the code that I am porting, so here it is:
// This is basically running an exe to compile a file that I create
short rtncod;
int GPDataAge = FileAge(SelectedPath + GPDATA); //Checks age of GPDATA if it exists
STARTUPINFO si; // Startup information structure
PROCESS_INFORMATION pi; // Process information structure
memset(&si, 0, sizeof(STARTUPINFO)); // Initializes STARTUPINFO to 0
si.cb = sizeof(STARTUPINFO); // Set the size of STARTUPINFO struct
AnsiString CmdLine = Config->ReadString("Configuration","CRTVSM","CRTVSM.EXE . -a"); // Windows version requires path
rtncod = (short)CreateProcess(
NULL, // pointer to name of executable module
CmdLine.c_str(), // pointer to command line string
NULL, // pointer to process security attributes
NULL, // pointer to thread security attributes
FALSE, // handle inheritance flag
CREATE_NEW_CONSOLE, // creation flags
NULL, // pointer to new environment block
NULL, // pointer to current directory name
&si, // pointer to STARTUPINFO
&pi); // pointer to PROCESS_INFORMATION
if (!rtncod) /*If rtncod was returned successful**/ {
int LastError = GetLastError();
if (LastError == 87 /* Lookup 87 error **/ && AnsiString(SelectedPath + GPDATA).Length() > 99)
ShowMessage("CRTASM could not run due to extremely long path name. Please map or move the folder to shorten the path");
else
ShowMessage("Could not compile VSMInfo.dat =>" + IntToStr(LastError));
}
else /* If successful **/ {
unsigned long ExitCode;
// CartTools will 'lock up' while waiting for CRTASM
do {
rtncod = GetExitCodeProcess(pi.hProcess,&ExitCode);
} while (rtncod && ExitCode == STILL_ACTIVE);
if (rtncod == 0) {
rtncod = GetLastError();
ShowMessage("Could not watch CRTVSM compile VSMInfo.dat =>" + IntToStr(GetLastError()));
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
if (GPDataAge == FileAge(SelectedPath + GPDATA)) // date/time didn't change!
Application->MessageBox(AnsiString("Output blocking file (" + SelectedPath + GPDATA") failed to be updated. Check operation of CRTVSM.EXE before using "GPDATA" with SAM/CMS!").c_str(),"CRTVSM Error",MB_OK|MB_ICONHAND);
All of this may not be relevant, and you may not know where my personal elements come from, but that is okay as I am only concerned with the MICROSOFT process elements (such as CreateProcess and STARTUPINFO).
So far I have looked at the Process.Start method provided in this question, but do not think that it allows me to go through the same processes as the ones listed above.
My question is, what class or methods can I use to customize my exe launch in a equivalent manner to the launch that is performed in the c++ code above?
UPDATE: Currently, I have the executable file located inside a folder that I created in the solution of my program. To launch the executable I am using the ProcessStartInfo class.
//The folder that the exe is located in is called "Executables"
ProcessStartInfo startInfo = new ProcessStartInfo("Executables\\MYEXECUTABLE.EXE");
Process.Start(startInfo);
Whenever I run the above lines of code I get a Win32Exception was unhandled, and it says that "The system cannot find the file specified".
The C++ code isn't using a command 'prompt', per se, but launching a process by providing a path the the executable to CreateProcess. You can accomplish the same thing in C# with the Process class. Configure Process.StartInfo and call the Start method.
Regarding launching the executable with a specific path: if you don't specify a full path then you are at the mercy of the working directory. If the exe is the same directory as the running executable, or a subdirectory of it, then you can construct the path like this:
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"Executables\MYEXECUTABLE.EXE");
ProcessStartInfo startInfo = new ProcessStartInfo(path);
Process.Start(startInfo);
Adding on to jltrem, an example of Process.Start is:
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo(v=vs.110).aspx
using System;
using System.Diagnostics;
using System.ComponentModel;
namespace MyProcessSample
{
class MyProcess
{
// Opens the Internet Explorer application.
void OpenApplication(string myFavoritesPath)
{
// Start Internet Explorer. Defaults to the home page.
Process.Start("IExplore.exe");
// Display the contents of the favorites folder in the browser.
Process.Start(myFavoritesPath);
}
// Opens urls and .html documents using Internet Explorer.
void OpenWithArguments()
{
// url's are not considered documents. They can only be opened
// by passing them as arguments.
Process.Start("IExplore.exe", "www.northwindtraders.com");
// Start a Web page using a browser associated with .html and .asp files.
Process.Start("IExplore.exe", "C:\\myPath\\myFile.htm");
Process.Start("IExplore.exe", "C:\\myPath\\myFile.asp");
}
// Uses the ProcessStartInfo class to start new processes,
// both in a minimized mode.
void OpenWithStartInfo()
{
ProcessStartInfo startInfo = new ProcessStartInfo("IExplore.exe");
startInfo.WindowStyle = ProcessWindowStyle.Minimized;
Process.Start(startInfo);
startInfo.Arguments = "www.northwindtraders.com";
Process.Start(startInfo);
}
static void Main()
{
// Get the path that stores favorite links.
string myFavoritesPath =
Environment.GetFolderPath(Environment.SpecialFolder.Favorites);
MyProcess myProcess = new MyProcess();
myProcess.OpenApplication(myFavoritesPath);
myProcess.OpenWithArguments();
myProcess.OpenWithStartInfo();
}
}
}

Changing a programs process name in task manager?

Okay so I've been looking around and I can't find an answer anywhere.
What I want my program to do is every time I run it, the name that shows up in the task manager is randomized.
There is a program called 'Liberation' that when you run it, it will change the process name to some random characters like AeB4B3wf52.tmp or something. I'm not sure what it is coded in though, so that might be the issue.
Is this possible in C#?
Edit:
I made a sloppy work around, I created a separate program that will check if there is a file named 'pb.dat', it will copy it to the temp folder, rename it to a 'randomchars.tmp' and run it.
Code if anyone was interested:
private void Form1_Load(object sender, EventArgs e)
{
try
{
if (!Directory.Exists(Environment.CurrentDirectory + #"\temp")) // Create a temp directory.
Directory.CreateDirectory(Environment.CurrentDirectory + #"\temp");
DirectoryInfo di = new DirectoryInfo(Environment.CurrentDirectory + #"\temp");
foreach (FileInfo f in di.GetFiles()) // Cleaning old .tmp files
{
if (f.Name.EndsWith(".tmp"))
f.Delete();
}
string charList = "abcdefghijklmnopqrstuvwxyz1234567890";
char[] trueList = charList.ToCharArray();
string newProcName = "";
for (int i = 0; i < 8; i++) // Build the random name
newProcName += trueList[r.Next(0, charList.Length)];
newProcName += ".tmp";
if (File.Exists(Environment.CurrentDirectory + #"\pb.dat")) // Just renaming and running.
{
File.Copy(Environment.CurrentDirectory + #"\pb.dat", Environment.CurrentDirectory + #"\temp\" + newProcName);
ProcessStartInfo p = new ProcessStartInfo();
p.FileName = Environment.CurrentDirectory + #"\temp\" + newProcName;
p.UseShellExecute = false;
Process.Start(p);
}
}
catch (Exception ex)
{
MessageBox.Show("I caught an exception! This is a bad thing...\n\n" + ex.ToString(), "Exception caught!");
}
Environment.Exit(-1); // Close this program anyway.
}
The process name in the task manager bases on the executable name without the extension, which you can not change while it is running.
Read the documentation:
The ProcessName property holds an executable file name, such as
Outlook, that does not include the .exe extension or the path. It is
helpful for getting and manipulating all the processes that are
associated with the same executable file.
in visual studio go to Project - Properties - Application - Assembly information and change Title
I would implement a host application to do this that simply runs and monitors a sub process (other executable). You may rename a file as such:
System.IO.File.Move("oldfilename", "newfilename");
and start the process like this:
Process.Start("newfilename");
This would mean that instead of one process you would have two, but the owner process only needs to be alive under startup - in order to change the name.

%AllUsersProfile%(%PROGRAMDATA%) gives a repetitive file path

I have an application written in C#, and I am seeking to write some information to the hidden ProgramData in order to access the same connection string from both the application's front end and back end.
I am accessing the directory using path variables as follows:
private bool ProgramDataWriteFile(string contentToWrite)
{
try
{
string strProgramDataPath = "%PROGRAMDATA%";
string directoryPath = Environment.ExpandEnvironmentVariables(strProgramDataPath) + "\\MyApp\\";
string path = Environment.ExpandEnvironmentVariables(strProgramDataPath)+"\\MyApp\\ConnectionInfo.txt";
if (Directory.Exists(directoryPath))
{
System.IO.StreamWriter file = new System.IO.StreamWriter(path);
file.Write(contentToWrite);
file.Close();
}
else
{
Directory.CreateDirectory(directoryPath);
System.IO.StreamWriter file = new System.IO.StreamWriter(path);
file.Write(contentToWrite);
file.Close();
}
return true;
}
catch (Exception e)
{
}
return false;
}
This seems to work correctly. However, my question is, when I used this path variable: %AllUsersProfile%(%PROGRAMDATA%)
instead, it expanded into an illegal(and redundant) file path : C:\ProgramData(C:\ProgramData)\
However, I thought that the latter path variable was the correct full name. Was I just using it incorrectly? I need to ensure that this connection info will be accessible to all users, will just using %PROGRAMDATA% allow that? I am using Windows 7 in case that is relevant.
From here:
FOLDERID_ProgramData / System.Environment.SpecialFolder.CommonApplicationData
The user would never want to browse here in Explorer, and settings changed here should affect every user on the machine. The default location is %systemdrive%\ProgramData, which is a hidden folder, on an installation of Windows Vista. You'll want to create your directory and set the ACLs you need at install time.
So, just use %PROGRAMDATA%, or better still:
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)

DirectoryNotFoundException when calling Directory.GetDirectories on Environment.SpecialFolder.Favorites due to Domain Folder Redirection

I have some C# code that tries to get the Favorites for the currently logged in user. The code is part of a Taskbar Toolbar that gets loaded into the Windows Explorer process. I have a user who is using Windows Vista with UAC enabled on a domain that either has Roaming Profiles or Folder Redirection setup and enabled. When calling Directory.GetDirectories on the Favorites path, it throws "System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\Users\\Favorites\". Other users on other domains that do not have Roaming Profiles or Folder Redirection setup do not have this issue.
The user also reported that copying the path from the failed logs into the run prompt fails to load the path, but if they navigate to the path directly using explorer and then copy and paste that path into the run prompt, it works. He sent me both paths and they are exactly identical which doesn't make any sense at all.
My theory is that this is caused by the Folder Redirection where that path is actually pointing to a share on the server but the redirection is failing when trying to access the subdirectories (of the directoryInfo returned from Directory.GetDirectories). The initial directory works but all subdirectories of the initial directory fail to redirect correctly.
Has anyone come across a situation like this and/or know a workaround to gain proper access to redirected folders?
private void GetFavorites()
{
try
{
System.IO.DirectoryInfo dirInfo = new System.IO.DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Favorites));
AddFavorites(dirInfo);
}
catch
{
}
}
private void AddFavorites(DirectoryInfo dirInfo)
{
foreach (System.IO.FileInfo fileInfo in dirInfo.GetFiles("*.url"))
{
//string alias = fileInfo.Name.Replace(".url", "");
if (!ItemsBookmarks.ContainsKey(fileInfo.Name))
ItemsBookmarks.Add(fileInfo.Name, fileInfo.Name);
}
foreach (System.IO.FileInfo fileInfo in dirInfo.GetFiles("*.lnk"))
{
if (!ItemsBookmarks.ContainsKey(fileInfo.Name))
ItemsBookmarks.Add(fileInfo.Name, fileInfo.Name);
}
foreach (System.IO.DirectoryInfo objDir in dirInfo.GetDirectories())
{
AddFavorites(objDir);
}
}
Thanks,
John
I believe the problem you are experiencing is related to Reparse Points.
See: http://msdn.microsoft.com/en-us/library/bb513869.aspx
See: What is the best way to check for reparse point in .net (c#)?
The problem can be avoided by using the following syntax:
private void AddFavorites(string dirPath)
{
try
{
foreach (string fileName in Directory.GetFiles(dirPath, "*.*", SearchOption.TopDirectoryOnly))
{
//string alias = fileInfo.Name.Replace(".url", "");
if (!ItemsBookmarks.ContainsKey(fileInfo.Name))
{
ItemsBookmarks.Add(fileName);
}
}
foreach (string subDirName in Directory.GetDirectories(dirPath, "*.*", SearchOption.TopDirectoryOnly))
{
AddFavorites(objDir);
}
}
catch
{
//error getting files or subdirs... permissions issue?
//throw
}
}

Categories

Resources