strange behaviour in our production environment, these days.
I have the following:
try {
var sourcePath = ... // my source path
var destinationPath = ... // guess what? You're right, my destination path
File.Copy(sourcePath, destinationPath);
Log.Debug(string.Format("Image {0} copied successfully", imagename));
}
catch(Exception e) {
// exception handling
}
Both source and destination path are on a network share, a folder on an other (virtual) machine with a large number of files (> 500k).
From the last 2 days, the code above runs, logs the last line (the one stating that image have been copied), but if I check in the destination folder, the supposed destination file does not exist.
I thought that for any I/O error File.Copy would raise an exception, so this thing is driving me mad.
Please note that other code parts that write files in that folder are working correctly. Also, note that all files names are unique (business code not included for brevity is making sure of that), and I think an exception would be thrown or the file would be at least overwritten, in that case.
Has anyone faced the same problem? Possible causes? Any solution?
EDIT 2016-07-01 15:12 (GMT+0200)
Ok, folks, apparently files aren't being deleted at all... simply for apparently no reason at all, after they are copied they're left open in read+write mode from the client connected user.
I found this trying running the reader application on my computer, in debug mode, and trying to open one of the files i knew that were copied recently.
I got an exception stating that the file was opened by someone else, and that seemed weird to me.
Opening Computer Management in the remote server (the one which stores the files), then going to Shared Folders > Open Files, I found that the file was left open in read+write mode from the impersonated user that the web application that copies the files is impersonating to do that job.
Also a whole bunch of other files where in the same conditions, and many others where open in read mode.
I found also in Shared Folders > Sessions, an astronomical long list of session of the impersonated user, all with long idle time.
Since impersonation is used only to copy the files, and then is disposed, I shouldn't expect that, right?
I think maybe there is a problem in the way we impersonate the user during file copy, linked to the large number of files in the destination folder.
I'll check that.
END EDIT
Thanks,
Claudio Valerio
Found a solution, I think.
My problem was the code used for impersonating the user with write permissions on the destination folder.
(In my defence, all this project have been inherited from previous software company, and it's pretty massive, so keeping an eye on everything isn't that easy)
The impersonation process was wrapped in a class implementing IDisposable
public class Impersonator :
IDisposable
{
public Impersonator()
{
string userName = // get username from config
string password = // get password from config
string domainName = // get domain from config
ImpersonateValidUser(userName, domainName, password);
}
public void Dispose()
{
UndoImpersonation();
}
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int LogonUser(
string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DuplicateToken(
IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(
IntPtr handle);
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_PROVIDER_DEFAULT = 0;
private void ImpersonateValidUser(
string userName,
string domain,
string password)
{
WindowsIdentity tempWindowsIdentity = null;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
try
{
if (RevertToSelf())
{
if (LogonUser(
userName,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
finally
{
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
if (tokenDuplicate != IntPtr.Zero)
{
CloseHandle(tokenDuplicate);
}
}
}
private void UndoImpersonation()
{
if (impersonationContext != null)
{
impersonationContext.Undo();
}
}
private WindowsImpersonationContext impersonationContext = null;
}
This class is used this way:
using(new Impersonator())
{
// do stuff with files in here
}
My suspect was that closing the handlers of the impersonated user, somehow, it could break something in the way that windows handles file open by the impersonated user through a network share, as in my case, leaving shared files open in read+write mode, preventing any other process/user to open them.
I modified the Impersonator class as follows:
public class Impersonator :
IDisposable
{
public Impersonator()
{
string userName = // get username from config
string password = // get password from config
string domainName = // get domain from config
ImpersonateValidUser(userName, domainName, password);
}
public void Dispose()
{
UndoImpersonation();
impersonationContext.Dispose();
}
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int LogonUser(
string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DuplicateToken(
IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(
IntPtr handle);
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_PROVIDER_DEFAULT = 0;
private void ImpersonateValidUser(
string userName,
string domain,
string password)
{
WindowsIdentity tempWindowsIdentity = null;
token = IntPtr.Zero;
tokenDuplicate = IntPtr.Zero;
try
{
if (RevertToSelf())
{
if (LogonUser(
userName,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
finally
{
}
}
private void UndoImpersonation()
{
try
{
if (impersonationContext != null)
{
impersonationContext.Undo();
}
}
finally
{
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
if (tokenDuplicate != IntPtr.Zero)
{
CloseHandle(tokenDuplicate);
}
}
}
private WindowsImpersonationContext impersonationContext = null;
private IntPtr token;
private IntPtr tokenDuplicate;
}
Basically I moved the handlers closing in the UndoImpersonation method. Also I had a doubt about leaving the impersonationContext not explicitly disposed, si I disposed it in the Dispose method of the Impersonator class.
Since I put in production this update, I hadn't any other issue with this code, and not any other shared file left open in read+write mode on the destination server.
Maybe not the optimal solution (I still have a whole bunch of sessions in Computer Management > Shared Folders > Sessions, but this seems not harming the system, for now.
If anyone have some additional comment, suggestion or depth study about this situation, I will be please to read.
Thanks,
Claudio
Related
I have a service that makes use of MemoryMappedFiles for interprocess communication. It has worked great for many years and was developed in .NET Framework 4.6.1. Now comes the time to port the code to .NET 6. I've gotten the bulk of it to work correctly except for one issue: the security ACL for the memory mapped file. That argument seems to have disappeared in .NET 6.
Here is a snippet from the 4.6.1 Framework version
fs = new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
FileSecurity fSec = File.GetAccessControl(FileName);
fSec.AddAccessRule(new FileSystemAccessRule("everyone", FileSystemRights.FullControl, AccessControlType.Allow));
File.SetAccessControl(FileName, fSec);
if (fs.Length == 0)
fs.SetLength(_SectionSize);
long fLen = fs.Length;
MemoryMappedFileSecurity security = new MemoryMappedFileSecurity();
security.AddAccessRule(new AccessRule<MemoryMappedFileRights>("everyone", MemoryMappedFileRights.FullControl, AccessControlType.Allow));
//Name = #"Global\DCCCache"; // "Global\" when running as a service so session 0 stuff available to everyone
_MMFHandle = MemoryMappedFile.CreateFromFile(fs, Name, _SectionSize, MemoryMappedFileAccess.ReadWrite, security, HandleInheritability.Inheritable, false);
_VAHandle = _MMFHandle.CreateViewAccessor();
This all works and allows non-admin user processes access to the memory mapped file.
.NET 6 drops the security argument from the .CreateFromFile method. As a result, only processes running with Administrator privileges have access to the memory mapped file. An "Access Denied" IO exception is thrown from the OpenExisting method of MemoryMappedFile for non-admin processes.
Is there a way to modify the security when I create a memory mapped file so non-admin processes have access?
I did a workaround. Put together the call I needed to create the memory mapped file with the correct security and call it once in the service that owns it. I changed the mainline code to use OpenExisting after the call to the routine below so the remainder of the code can use the .NET 6 library as intended. Not ideal, but it fixed the issue.
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace MMFService {
internal class MMFNet6Shim : IDisposable {
private bool win32Result = false;
private int cbSid = SECURITY_MAX_SID_SIZE;
private SECURITY_ATTRIBUTES securityAttributes = new SECURITY_ATTRIBUTES();
private SafeMemoryMappedFileHandle hFile;
private const int SDDL_REVISION_1 = 1;
private const int SECURITY_MAX_SID_SIZE = 68;
private const int PAGE_READWRITE = 0x04;
private const int FILE_MAP_WRITE = 0X02;
public MMFNet6Shim(FileStream fs, string DBName) {
win32Result = ConvertStringSecurityDescriptorToSecurityDescriptor("D:(A;OICI;GA;;;WD)", SDDL_REVISION_1, out securityAttributes.lpSecurityDescriptor, IntPtr.Zero);
if (!win32Result)
throw new Exception("ConvertStringSecurityDescriptorToSecurityDescriptor", new Win32Exception(Marshal.GetLastWin32Error()));
securityAttributes.nLength = Marshal.SizeOf(securityAttributes);
securityAttributes.bInheritHandle = false;
long fLen = fs.Length;
hFile = CreateFileMapping(fs.SafeFileHandle, ref securityAttributes, PAGE_READWRITE, 0, Convert.ToInt32(fLen), DBName);
if (hFile.IsInvalid)
throw new Exception("CreateFileMapping", new Win32Exception(Marshal.GetLastWin32Error()));
}
public void Dispose() {
if(!hFile.IsInvalid)
hFile.Close();
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES {
public int nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor
(
[In] string StringSecurityDescriptor,
[In] int StringSDRevision,
[Out] out IntPtr SecurityDescriptor,
[Out] IntPtr SecurityDescriptorSize
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree([In] IntPtr hMem);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeMemoryMappedFileHandle CreateFileMapping(
[In] SafeFileHandle hFile,
[In][Optional] ref SECURITY_ATTRIBUTES lpAttributes,
[In] int flProtect,
[In] int dwMaximumSizeHigh,
[In] int dwMaximumSizeLow,
[In][Optional] string lpName
);
}
}
The easiest way is to set access for Everyone:
using System.Runtime.InteropServices;
[DllImport("advapi32.dll", EntryPoint = "SetSecurityInfo", CallingConvention = CallingConvention.Winapi,
SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
private static extern uint SetSecurityInfoByHandle(SafeHandle handle, uint objectType, uint securityInformation,
byte[]? owner, byte[]? group, byte[]? dacl, byte[]? sacl);
then
mmfile = MemoryMappedFile.CreateNew("Global\\jdfghdfghsd", requiredsize, MemoryMappedFileAccess.ReadWrite);
if (SetSecurityInfoByHandle(mmfile.SafeMemoryMappedFileHandle, 1, 4, null, null, null, null) != 0)
throw new Exception("MemoryMappedFile set security failed");
Set customized access rights if required, look at SetSecurityInfo help
So, sample apps:
App1:
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
[DllImport("advapi32.dll", EntryPoint = "SetSecurityInfo", CallingConvention = CallingConvention.Winapi,
SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
static extern uint SetSecurityInfoByHandle(SafeHandle handle, uint objectType, uint securityInformation,
byte[]? owner, byte[]? group, byte[]? dacl, byte[]? sacl);
var mmfile = MemoryMappedFile.CreateNew("Global\\dsfgsdfsdf", 4, MemoryMappedFileAccess.ReadWrite);
if (SetSecurityInfoByHandle(mmfile.SafeMemoryMappedFileHandle, 1, 4, null, null, null, null) != 0)
throw new Exception("Access rights set up failed");
mmfile.CreateViewAccessor(0, 4).Write(0, 234);
Console.ReadLine();
App2:
using System.IO.MemoryMappedFiles;
Console.WriteLine(MemoryMappedFile.OpenExisting("Global\\dsfgsdfsdf", MemoryMappedFileRights.ReadWrite).CreateViewAccessor(0, 4).ReadInt32(0));
Console.ReadLine();
Run app1, then app2. It should emit 234. Then press Enter for app1.
Be sure the user runs app1 is able to create the global objects (by default only administrators and services are allowed)
I am using the following code to copy a file to a directory where only one X user has access, I am using impersonation but when I use it the CopyFileEx does not work, but I don't know why. if I remove the part of the impersonation it works correctly but I need it to be copied with a user X since in production it has to be like that.
ImpersonationUtils impersonation = new ImpersonationUtils();
var token = impersonation.LogonAsUser("User", "Domain", "pwd");
if (!IntPtr.Equals(token, IntPtr.Zero))
{
System.Security.Principal.WindowsImpersonationContext impersonatedUser = null;
var newIdentity = new System.Security.Principal.WindowsIdentity(token);
impersonatedUser = newIdentity.Impersonate();
bool result = CopyFileEx(filename, tempFilepath, new CopyProgressRoutine(this.CopyProgressHandler), IntPtr.Zero, cancelp, 0);
if (impersonatedUser != null)
impersonatedUser.Undo();
impersonation.LogonAsUserEnd(token);
}
Here is a class I wrote in the past to make operations under Impersonation,
Check out how the token is created inside DoWorkUnderImpersonation()
with the credentials and the required constants to LogonUser() of advapi32.dll.
The required operation is made inside the DoWork() method, add your copy files logic there.
Call the static method DoWorkUnderImpersonation() from out side
// Implementation of the Impersonation class
Impersonation.DoWorkUnderImpersonation("DOMAIN", "USER", "PASSWORD");
public static class Impersonation
{
// obtains user token
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string pszUsername, string pszDomain, string pszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
// closes open handes returned by LogonUser
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
public static void DoWorkUnderImpersonation(string _domain, string _userName, string _password)
{
//elevate privileges before doing file copy to handle domain security
WindowsImpersonationContext impersonationContext = null;
IntPtr userHandle = IntPtr.Zero;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
string domain = _domain;
string user = _userName;
string password = _password;
try
{
Debug.WriteLine("windows identify before impersonation: " + WindowsIdentity.GetCurrent().Name);
// if domain name was blank, assume local machine
if (domain == "")
domain = System.Environment.MachineName;
// Call LogonUser to get a token for the user
bool loggedOn = LogonUser(user,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref userHandle);
if (!loggedOn)
{
Debug.WriteLine("Exception impersonating user, error code: " + Marshal.GetLastWin32Error());
return;
}
// Begin impersonating the user
using (impersonationContext = WindowsIdentity.Impersonate(userHandle))
{
Debug.WriteLine("Main() windows identify after impersonation: " + WindowsIdentity.GetCurrent().Name);
//run the program with elevated privileges (like file copying from a domain server)
DoWork();
}
}
catch (Exception ex)
{
Console.Write("Exception impersonating user: " + ex.Message);
}
finally
{
// Clean up
if (impersonationContext != null)
{
impersonationContext.Undo();
}
if (userHandle != IntPtr.Zero)
{
CloseHandle(userHandle);
}
}
}
private static void DoWork()
{
try
{
// MAKE YOUR REQUIRED TASK HERE UNDER IMPERSONATION
}
catch (Exception ex)
{
Console.Write("error in Impersonation.DoWork() executing a task: " + ex.Message);
}
}
}
I have a folder in Windows Server with subfolders and ≈50000 files. When I click the right mouse button and choose delete (or shift+delete) – all files are deleted in 10-20 seconds.
When I delete files using code – 1500-4000 seconds.
Delete large number of files – don't work for me.
My code:
string folderPath = #"C://myFolder";
DirectoryInfo folderInfo = new DirectoryInfo(folderPath);
folderInfo.Delete(true); // true - recursive, with sub-folders
How to delete files faster?
A much faster way to delete files is to use the Windows functions instead of the .NET ones.
You will need to first import the function:
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteFile(string lpFileName);
And then you can do this:
string[] files = Directory.EnumerateFiles(path, "*". SearchOption.AllDirectories);
foreach (string file in files)
{
DeleteFile(file);
}
Once the files are deleted, which is the slowest part by using the managed APIs, you can call Directory.DeleteFolder(path, true) to delete the empty folders.
Since the question is actually about deleting network shared folders and it's stated that the explorer based delete is much faster than the C# internal delete mechanism, it might help to just invoke a windows shell based delete.
ProcessStartInfo Info = new ProcessStartInfo();
Info.Arguments = "/C rd /s /q \"<your-path>\"";
Info.WindowStyle = ProcessWindowStyle.Hidden;
Info.CreateNoWindow = true;
Info.FileName = "cmd.exe";
Process.Start(Info);
Ofcourse, you have to replace <your-path>.
However, I don't have the infrastructure and files available to test the performance myself right now.
Not quite sure why the method DirectoryInfo.Delete() takes too much time when deleting folders that have a lot of files and sub-folders. I suspect that the method may also do quite a few things that are unnecessary.
I write a small class to to use Win API without doing too many unnecessary things to test my idea. It takes about 40 seconds to delete a folder that have 50,000 files and sub-folders. So, hope it helps.
I use this PowerScript to generate the testing files.
$folder = "d:\test1";
For ($i=0; $i -lt 50000; $i++)
{
New-Item -Path $folder -Name "test$i.txt" -ItemType "file" -Value $i.ToString();
}
The following is the code in C#.
using System;
using System.Collections.Generic;
//
using System.Runtime.InteropServices;
using System.IO;
//
namespace TestFileDelete
{
class FileDelete
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct WIN32_FIND_DATAW
{
public FileAttributes dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public UInt32 nFileSizeHigh; // DWORD
public UInt32 nFileSizeLow; // DWORD
public UInt32 dwReserved0; // DWORD
public UInt32 dwReserved1; // DWORD
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public String cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public String cAlternateFileName;
};
static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr FindFirstFileW(String lpFileName, out WIN32_FIND_DATAW lpFindFileData);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern Boolean FindNextFileW(IntPtr hFindFile, out WIN32_FIND_DATAW lpFindFileData);
[DllImport("kernel32.dll")]
private static extern Boolean FindClose(IntPtr handle);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern Boolean DeleteFileW(String lpFileName); // Deletes an existing file
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern Boolean RemoveDirectoryW(String lpPathName); // Deletes an existing empty directory
// This method check to see if the given folder is empty or not.
public static Boolean IsEmptyFolder(String folder)
{
Boolean res = true;
if (folder == null && folder.Length == 0)
{
throw new Exception(folder + "is invalid");
}
WIN32_FIND_DATAW findFileData;
String searchFiles = folder + #"\*.*";
IntPtr searchHandle = FindFirstFileW(searchFiles, out findFileData);
if (searchHandle == INVALID_HANDLE_VALUE)
{
throw new Exception("Cannot check folder " + folder);
}
do
{
if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
{
// found a sub folder
if (findFileData.cFileName != "." && findFileData.cFileName != "..")
{
res = false;
break;
}
} // if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
else
{
// found a file
res = false;
break;
}
} while (FindNextFileW(searchHandle, out findFileData));
FindClose(searchHandle);
return res;
} // public static Boolean IsEmptyFolder(String folder)
// This method deletes the given folder
public static Boolean DeleteFolder(String folder)
{
Boolean res = true;
// keep non-empty folders to delete later (after we delete everything inside)
Stack<String> nonEmptyFolder = new Stack<String>();
String currentFolder = folder;
do
{
Boolean isEmpty = false;
try
{
isEmpty = IsEmptyFolder(currentFolder);
}
catch (Exception ex)
{
// Something wrong
res = false;
break;
}
if (!isEmpty)
{
nonEmptyFolder.Push(currentFolder);
WIN32_FIND_DATAW findFileData;
IntPtr searchHandle = FindFirstFileW(currentFolder + #"\*.*", out findFileData);
if (searchHandle != INVALID_HANDLE_VALUE)
{
do
{ // for each folder, find all of its sub folders and files
String foundPath = currentFolder + #"\" + findFileData.cFileName;
if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
{
// found a sub folder
if (findFileData.cFileName != "." && findFileData.cFileName != "..")
{
if (IsEmptyFolder(foundPath))
{ // found an empty folder, delete it
if (!(res = RemoveDirectoryW(foundPath)))
{
Int32 error = Marshal.GetLastWin32Error();
break;
}
}
else
{ // found a non-empty folder
nonEmptyFolder.Push(foundPath);
}
} // if (findFileData.cFileName != "." && findFileData.cFileName != "..")
} // if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
else
{
// found a file, delete it
if (!(res = DeleteFileW(foundPath)))
{
Int32 error = Marshal.GetLastWin32Error();
break;
}
}
} while (FindNextFileW(searchHandle, out findFileData));
FindClose(searchHandle);
} // if (searchHandle != INVALID_HANDLE_VALUE)
}// if (!IsEmptyFolder(folder))
else
{
if (!(res = RemoveDirectoryW(currentFolder)))
{
Int32 error = Marshal.GetLastWin32Error();
break;
}
}
if (nonEmptyFolder.Count > 0)
{
currentFolder = nonEmptyFolder.Pop();
}
else
{
currentFolder = null;
}
} while (currentFolder != null && res);
return res;
} // public static Boolean DeleteFolder(String folder)
};
class Program
{
static void Main(string[] args)
{
DateTime t1 = DateTime.Now;
try
{
Boolean b = FileDelete.DeleteFolder(#"d:\test1");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
DateTime t2 = DateTime.Now;
TimeSpan ts = t2 - t1;
Console.WriteLine(ts.Seconds);
}
}
}
I need to install different setups silently with administrator privileges.
I have to hard code the privileges because the users don´t know username and password to install the setups themselfes.
I have tried two different approaches.
ProcessStartInfo with UserName, Password and UseShellExecute = false.
User Impersonation with
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(...);
In both scenarios windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator) returns false
and my setups do not run because of insufficient rights.
Strange behavior: LogonUser always returns true, even with invalid credentials.
Here is the impersonation class:
namespace BlackBlade.Utilities
{
/// <summary>
/// Quelle: http://www.blackbladeinc.com/en-us/community/blogs/archive/2009/08/10/runas-in-c.aspx
/// </summary>
public class SecurityUtilities
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
public delegate void RunAsDelegate();
public static void RunAs(RunAsDelegate methodToRunAs, string username, string password)
{
string userName;
string domain;
if (username.IndexOf('\\') > 0)
{
//a domain name was supplied
string[] usernameArray = username.Split('\\');
userName = usernameArray[1];
domain = usernameArray[0];
}
else
{
//there was no domain name supplied
userName = username;
domain = ".";
}
RunAs(methodToRunAs, userName, password, domain);
}
public static void RunAs(RunAsDelegate methodToRunAs, string username, string password, string domain)
{
IntPtr userToken;
WindowsIdentity adminIdentity = null;
WindowsImpersonationContext adminImpersonationContext = null;
try
{
if (LogonUser(username, string.IsNullOrEmpty(domain) ? "." : domain, password, 9, 0, out userToken))
{
//the impersonation suceeded
adminIdentity = new WindowsIdentity(userToken);
adminImpersonationContext = adminIdentity.Impersonate();
// todo: Entfernen.
WindowsPrincipal p = new WindowsPrincipal(adminIdentity);
MessageBox.Show(p.IsInRole(WindowsBuiltInRole.Administrator).ToString());
//run the delegate method
//methodToRunAs();
}
else
throw new Exception(string.Format("Could not impersonate user {0} in domain {1} with the specified password.", username, domain));
}
catch (Exception se)
{
int ret = Marshal.GetLastWin32Error();
if (adminImpersonationContext != null)
adminImpersonationContext.Undo();
throw new Exception("Error code: " + ret.ToString(), se);
}
finally
{
//revert to self
if (adminImpersonationContext != null)
adminImpersonationContext.Undo();
}
}
}
}
Add a manifest to the process you are starting with RunAs to request elevation.
Edit: First, start a process using your known administrator credentials, either with LogonUser/CreateProcessAsUser or with CreateProcessWithLogon. Then check for real admin rights (maybe UAC is turned off) and if necessary, have this process (running as non-elevated administrator) start another copy of itself with ShellExecuteEx using the runas verb. This is the only way. UAC was explicitly designed to prohibit elevation without user confirmation.
Users will have to confirm the elevation, unless UAC is turned off. For better user experience (less scary message box) get a code signing certificate and sign this executable.
Use Group Policy to roll out the MSI or EXE installer.
Alternatively use Task Scheduler to run the installer as the Local System account.
Have you tried setting dwLogonType to 2 instead of 9?
http://msdn.microsoft.com/en-us/library/windows/desktop/bb540756(v=vs.85).aspx
Here's a code sample which works for me:
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
WindowsImpersonationContext impersonationContext;
[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
WindowsIdentity tempWindowsIdentity;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
if (RevertToSelf())
{
if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
if (impersonationContext != null)
{
CloseHandle(token);
CloseHandle(tokenDuplicate);
return true;
}
}
}
}
if (token != IntPtr.Zero)
CloseHandle(token);
if (tokenDuplicate != IntPtr.Zero)
CloseHandle(tokenDuplicate);
According the function description in "http://msdn.microsoft.com/en-us/library/cc196998%28v=VS.85%29.aspx", I wrote the following code to try to get IE protected cookies:
public static string GetProtectedModeCookie(string lpszURL, string lpszCookieName, uint dwFlags)
{
var size = 255;
var sb = new System.Text.StringBuilder(size);
var acturalSize = sb.Capacity;
var code = IEGetProtectedModeCookie(lpszURL, lpszCookieName, sb, ref acturalSize, dwFlags);
if ((code & 0x80000000) > 0) return string.Empty;
if (acturalSize > size)
{
sb.EnsureCapacity(acturalSize);
IEGetProtectedModeCookie(lpszURL, lpszCookieName, sb, ref acturalSize, dwFlags);
}
return sb.ToString();
}
[DllImport("ieframe.dll", SetLastError = true)]
public static extern uint IEGetProtectedModeCookie(string lpszURL, string lpszCookieName, System.Text.StringBuilder pszCookieData, ref int pcchCookieData, int dwFlags);
to test this function:
var cookies = GetProtectedModeCookie("http://bbs.pcbeta.com/", null, 0);
But the api IEGetProtectedModeCookie always return 0x80070057 which indicates that one or more arguments are invalid.
I was confused, after a lot of try finally failed, only get this result. Can anybody help me?
IEGetProtectedModeCookie will return E_INVALIDARG if it thinks that the URL isn't meant to open in Protected Mode. It determines this using the IEIsProtectedModeURL API. So if you've put that URL in the Trusted Zone or whatnot, then you'll hit this error. The underlying InternetGetCookie API will return E_INVALIDARG if you fail to pass a URL or fail to pass a pointer to an integer for the size of the buffer.
Also note that the IEGetProtectedModeCookie API will not work from a high integrity (e.g. Admin) process; it will return ERROR_INVALID_ACCESS (0x8000000C).
Here's the code I use:
[DllImport("ieframe.dll", CharSet = CharSet.Unicode, EntryPoint = "IEGetProtectedModeCookie", SetLastError = true)]
public static extern int IEGetProtectedModeCookie(String url, String cookieName, StringBuilder cookieData, ref int size, uint flag);
private void GetCookie_Click(object sender, EventArgs e)
{
int iSize = 4096;
StringBuilder sbValue = new StringBuilder(iSize);
int hResult = IEAPI.IEGetProtectedModeCookie("http://www.google.com", "PREF", sbValue, ref iSize, 0);
if (hResult == 0)
{
MessageBox.Show(sbValue.ToString());
}
else
{
MessageBox.Show("Failed to get cookie. HRESULT=0x" + hResult.ToString("x") + "\nLast Win32Error=" + Marshal.GetLastWin32Error().ToString(), "Failed");
}
}
Charset parameter must be exist in DllImport attribute. Change the API declartion to follow will works well:
[DllImport("ieframe.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint IEGetProtectedModeCookie(string lpszURL, string lpszCookieName, System.Text.StringBuilder pszCookieData, ref int pcchCookieData, uint dwFlags);
If Charset parameter missed, this API will always return 0x80070057 which indicates one or more arguments are invalid.