I'd like to read an EFS certificate (say from a pfx file) and use it temporarily to read/write some files. (I'd like it to not persist in any store once the program exits.) It looks like SetUserFileEncryptionKey might provide this functionality, but I get a bizarre return code (0x80092004) when I try it. Here's my code:
var x509Cert = new X509Certificate2(#"C:\Users\Public\Downloads\key.pfx", "<mypass>");
var certContext = Marshal.PtrToStructure<CertContext>(x509Cert.Handle);
var blob = new EfsCertificateBlob
{
dwCertEncodingType = certContext.dwCertEncodingType,
cbData = certContext.cbCertEncoded,
pbData = certContext.pbCertEncoded,
};
var pCertBlob = Marshal.AllocHGlobal(Marshal.SizeOf(blob));
Marshal.StructureToPtr(blob, pCertBlob, false);
var id = WindowsIdentity.GetCurrent();
var curStringSid = id.User?.Value;
Console.WriteLine(curStringSid);
ConvertStringSidToSid(curStringSid, out var sidPtr);
var certStruct = new EncryptionCertificate
{
cbTotalLength = (uint) Marshal.SizeOf(typeof(EncryptionCertificate)),
pUserSid = sidPtr,
pCertBlob = pCertBlob,
};
var res = SetUserFileEncryptionKey(certStruct);
Console.WriteLine($"Result: 0x{res:X}"); // Result: 0x80092004
Here too is my interop code:
[StructLayout(LayoutKind.Sequential)]
public class CertContext
{
public uint dwCertEncodingType;
public IntPtr pbCertEncoded;
public uint cbCertEncoded;
public IntPtr pCertInfo;
public IntPtr hCertStore;
}
[StructLayout(LayoutKind.Sequential)]
public class EfsCertificateBlob
{
public uint dwCertEncodingType;
public uint cbData;
public IntPtr pbData;
}
[StructLayout(LayoutKind.Sequential)]
public class EncryptionCertificate
{
public uint cbTotalLength;
public IntPtr pUserSid;
public IntPtr pCertBlob;
}
[DllImport("Advapi32.dll")]
public static extern uint SetUserFileEncryptionKey(EncryptionCertificate pEncryptionCertificate);
Does SetUserFileEncryptionKey do what I hope it does? And what am I doing wrong here?
(My use case is in working with sensitive data that I don't want the user to later be able to read or redistribute. So I'd like those files to be inaccessible as soon as the process terminates.)
It appears that 0x80092004 is CRYPT_E_NOT_FOUND and that SetUserFileEncryptionKey only works with certificates that are already part of the user's certificate store. When I import the relevant certificate, the above code returns ERROR_SUCCESS. It seems this function doesn't serve the use case that I hoped it did.
Related
I have WCF service that is hosted on windows service. I installed this service using Windows installer. Sometimes, when i stop service using C# code, it stucks on stopping. So i thought, why not kill service if service is not stopping within 2 minutes. My code is below to stop service:
var service = ServiceController.GetServices()
.FirstOrDefault(s => s.ServiceName == serviceName);
try
{
if (service == null || service.Status != ServiceControllerStatus.Running) return;
if(service.CanStop)
{
session.LogInfo($"Stopping '{serviceName}'.");
TimeSpan timeout = TimeSpan.FromMilliseconds(ServiceStopTime);
service.Stop();
service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
session.LogInfo($"'{serviceName}' stopped successfully.");
}
It is working as expected. I want to kill my process if service does not stop. Here is my code to kill process.
var processName = GetProcessNameByWindowsService(serviceName);
if (processName == null) return;
Process[] procs = Process.GetProcessesByName(processName);
if (procs.Length > 0)
{
foreach (Process proc in procs)
{
session.LogInfo($"Killing Process'{processName}'.");
proc.Kill();
session.LogInfo($"'{processName}' killed successfully.");
}
}
It is working as expected too but the problem is when i kill the process, the service does not stop. It assigns new process to service and service keep runs. After googled and investing some time i found the cause that is the window service recovery option which is restart the service if it fails. I want to change/set the recovery option for service in case of first failure, second failure and subsequent failure to take no action using C# code. I googled but did not find anything. So i want to know how i can change the recovery option of installed window service using C#?
After investing time finally i have found the solution with the help of this link. I have written two helper classes to set/update recovery option of windows service. First of all i wrote a static helper class which is below:
using System;
using System.Runtime.InteropServices;
namespace HRTC.CustomActions.Helpers
{
public static class ServiceRecoveryOptionHelper
{
//Action Enum
public enum RecoverAction
{
None = 0, Restart = 1, Reboot = 2, RunCommand = 3
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct ServiceFailureActions
{
public int dwResetPeriod;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpRebootMsg;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpCommand;
public int cActions;
public IntPtr lpsaActions;
}
[StructLayout(LayoutKind.Sequential)]
public class ScAction
{
public int type;
public uint dwDelay;
}
// Win32 function to open the service control manager
[DllImport("advapi32.dll")]
public static extern IntPtr OpenSCManager(string lpMachineName, string lpDatabaseName, int dwDesiredAccess);
// Win32 function to open a service instance
[DllImport("advapi32.dll")]
public static extern IntPtr OpenService(IntPtr hScManager, string lpServiceName, int dwDesiredAccess);
// Win32 function to change the service config for the failure actions.
[DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig2")]
public static extern bool ChangeServiceFailureActions(IntPtr hService, int dwInfoLevel,
[MarshalAs(UnmanagedType.Struct)]
ref ServiceFailureActions lpInfo);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "QueryServiceConfig2W")]
public static extern Boolean QueryServiceConfig2(IntPtr hService, UInt32 dwInfoLevel, IntPtr buffer, UInt32 cbBufSize, out UInt32 pcbBytesNeeded);
[DllImport("kernel32.dll")]
public static extern int GetLastError();
}
public class FailureAction
{
// Default constructor
public FailureAction() { }
// Constructor
public FailureAction(ServiceRecoveryOptionHelper.RecoverAction actionType, int actionDelay)
{
Type = actionType;
Delay = actionDelay;
}
// Property to set recover action type
public ServiceRecoveryOptionHelper.RecoverAction Type { get; set; } = ServiceRecoveryOptionHelper.RecoverAction.None;
// Property to set recover action delay
public int Delay { get; set; }
}
}
Then i already have static class for windows services that have different methods like to start windows service, stop windows service and install service etc. I added new static method in this class to change recovery option of windows service which receive 4 parameters. First one is the service name, and other three are the recovery options of first,second and subsequent recovery options respectively. Below is it's implementation.
using System;
using System.Collections;
using System.Runtime.InteropServices;
namespace HRTC.CustomActions.Helpers
{
public class LocalServiceHelper
{
//Change service recovery option settings
private const int ServiceAllAccess = 0xF01FF;
private const int ScManagerAllAccess = 0xF003F;
private const int ServiceConfigFailureActions = 0x2;
private const int ErrorAccessDenied = 5;
public static void ChangeRevoveryOption(string serviceName, ServiceRecoveryOptionHelper.RecoverAction firstFailureAction,
ServiceRecoveryOptionHelper.RecoverAction secondFailureAction, ServiceRecoveryOptionHelper.RecoverAction thirdFailureAction)
{
try
{
// Open the service control manager
var scmHndl = ServiceRecoveryOptionHelper.OpenSCManager(null, null, ScManagerAllAccess);
if (scmHndl.ToInt32() <= 0)
return;
// Open the service
var svcHndl = ServiceRecoveryOptionHelper.OpenService(scmHndl, serviceName, ServiceAllAccess);
if (svcHndl.ToInt32() <= 0)
return;
var failureActions = new ArrayList
{
// First Failure Actions and Delay (msec)
new FailureAction(firstFailureAction, 0),
// Second Failure Actions and Delay (msec)
new FailureAction(secondFailureAction, 0),
// Subsequent Failures Actions and Delay (msec)
new FailureAction(thirdFailureAction, 0)
};
var numActions = failureActions.Count;
var myActions = new int[numActions * 2];
var currInd = 0;
foreach (FailureAction fa in failureActions)
{
myActions[currInd] = (int) fa.Type;
myActions[++currInd] = fa.Delay;
currInd++;
}
// Need to pack 8 bytes per struct
var tmpBuf = Marshal.AllocHGlobal(numActions * 8);
// Move array into marshallable pointer
Marshal.Copy(myActions, 0, tmpBuf, numActions * 2);
// Set the SERVICE_FAILURE_ACTIONS struct
var config =
new ServiceRecoveryOptionHelper.ServiceFailureActions
{
cActions = 3,
dwResetPeriod = 0,
lpCommand = null,
lpRebootMsg = null,
lpsaActions = new IntPtr(tmpBuf.ToInt32())
};
// Call the ChangeServiceFailureActions() abstraction of ChangeServiceConfig2()
var result =
ServiceRecoveryOptionHelper.ChangeServiceFailureActions(svcHndl, ServiceConfigFailureActions,
ref config);
//Check the return
if (!result)
{
var err = ServiceRecoveryOptionHelper.GetLastError();
if (err == ErrorAccessDenied)
{
throw new Exception("Access Denied while setting Failure Actions");
}
// Free the memory
Marshal.FreeHGlobal(tmpBuf);
}
}
catch (Exception)
{
throw new Exception("Unable to set service recovery options");
}
}
}
}
That's it. You just only need to call the method to change recovery option of windows service. For example:
LocalServiceHelper.ChangeRevoveryOption("ServiceName",
ServiceRecoveryOptionHelper.RecoverAction.Restart,
ServiceRecoveryOptionHelper.RecoverAction.Restart,
ServiceRecoveryOptionHelper.RecoverAction.None);
It will update the recovery option of windows service as you will mention when calling the method. Hope this help. Happy codding! :)
So I've read the documentation and countless examples online how to marshal array of structures. I've marshalled array of int's, I've marshalled structures, but now I'm completely stuck and can't get it to work no matter what I've try. Been stuck on it for over a day now.
Structure/class, tried as both
[StructLayout(LayoutKind.Sequential,CharSet = CharSet.Unicode)]
public class SaveDetails
{
[MarshalAs(UnmanagedType.LPWStr)]
public string Log;
public FILETIME FileTime;
[MarshalAs(UnmanagedType.Bool)]
public bool Saved;
}
Pinvoke and call delegate
public class LogSaveFiles : IDisposable
{
[UnmanagedFunctionPointer(CallingConvention.Winapi,CharSet = CharSet.Unicode)]
private delegate Status DLogSaveFiles([ In, Out] SaveDetails[] logsToSave, string destinationPath);
private static DLogSaveFiles _dLogSaveFiles;
private IntPtr PLogSaveFiles { get; set; }
public bool LogSaveFilesAvailable => PLogSaveFiles != IntPtr.Zero;
public LogSaveFiles(Importer importer)
{
if (importer.dllLibraryPtr!= IntPtr.Zero)
{
PLogSaveFiles = Importer.GetProcAddress(importer.dllLibrary, "LogSaveFiles");
}
}
public Status SaveFiles(SaveDetails[] logsToSave,string destinationPath)
{
Status result = Status.FunctionNotAvailable;
if (LogSaveFilesAvailable)
{
_dLogSaveFiles = (DLogSaveFiles)Marshal.GetDelegateForFunctionPointer(PLogSaveFiles, typeof(DLogSaveFiles));
result = _dLogSaveFiles(logsToSave, destinationPath);
}
return result;
}
public void Dispose()
{
}
}
Call
private void SaveLogs()
{
var logsToSave = new[]{
new SaveDetails{
FileTime = new FILETIME {dwHighDateTime = 3,dwLowDateTime = 5},
Log = LogTypes.logDeviceLog,
Saved = true},
new SaveDetails{
FileTime = new FILETIME {dwHighDateTime = 1,dwLowDateTime = 2},
Log = LogTypes.logDeviceLog,
Saved = false}
};
var pathToSave = "C:\\Logs";
_logSaveFiles.SaveFiles(logsToSave, pathToSave);
}
c++ exposed call
typedef struct _LOG_SAVE_DETAILS
{
LPTSTR szLog;
FILETIME fromFileTime;
BOOL bSaved;
} LOG_SAVE_DETAILS, *PLOG_SAVE_DETAILS;
/* Function definitions */
ULY_STATUS _API LogSaveFiles (PLOG_SAVE_DETAILS ppLogs [],
LPCTSTR szDestinationPath);
Path to destination gets passed properly, but array of structures never goes through resulting in access violation when trying to access it. At first I thought it was issue with LPTSTR not going through properly but I've implemented other calls with it on its own and succeeded marshalling it through.
I've read everything on https://learn.microsoft.com/en-us/dotnet/framework/interop/marshaling-data-with-platform-invoke , it all indicates that my approach is correct, but it doesn't work.
Any help is appreciated.
Simple solution: C side change PLOG_SAVE_DETAILS ppLogs [] to LOG_SAVE_DETAILS ppLogs [], then C#-side change public class SaveDetails to public struct SaveDetails.
Marshaling array of objects seems to be difficult (I wasn't able to do it). Marshaling array of structs works. An alternative is to do the marshaling manually, but it is a pain.
The "pain" of manual marshaling (only modified lines of code):
[UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)]
private delegate Status DLogSaveFiles(IntPtr[] logsToSave, string destinationPath);
and then
public Status SaveFiles(SaveDetails[] logsToSave, string destinationPath)
{
Status result = Status.FunctionNotAvailable;
if (LogSaveFilesAvailable)
{
if (_dLogSaveFiles == null)
{
_dLogSaveFiles = (DLogSaveFiles)Marshal.GetDelegateForFunctionPointer(PLogSaveFiles, typeof(DLogSaveFiles));
}
int size = Marshal.SizeOf(typeof(SaveDetails));
IntPtr basePtr = IntPtr.Zero;
IntPtr[] ptrs = new IntPtr[logsToSave.Length + 1];
try
{
basePtr = Marshal.AllocHGlobal(size * logsToSave.Length);
for (int i = 0; i < logsToSave.Length; i++)
{
ptrs[i] = IntPtr.Add(basePtr, (i * size));
Marshal.StructureToPtr(logsToSave[i], ptrs[i], false);
}
result = _dLogSaveFiles(ptrs, destinationPath);
}
finally
{
if (basePtr != IntPtr.Zero)
{
for (int i = 0; i < logsToSave.Length; i++)
{
if (ptrs[i] != IntPtr.Zero)
{
Marshal.DestroyStructure(ptrs[i], typeof(SaveDetails));
}
}
Marshal.FreeHGlobal(basePtr);
}
}
}
return result;
}
Important: this is a marshaler C#->C++. The C++ mustn't modify the received array in any way or there will be a memory leak.
Is there any way to force closed any instance of a specific file on Server 2012?
For arguments sake, call the link to the file D:\Shares\Shared\Sharedfile.exe
I have C# code which copies the latest version of a program into this directory, which often has people using the program during the day. Closing open file handles and replacing the file has worked well, because it just means next time the user opens the program they have all the latest changes.
However it gets a little monotonous doing this from computer management on the server, so I was wondering if there was a way to do it from C# code?
I tried this but it doesn't have the right overloads that I need. Perhaps is there something in the File class I could use?
EDIT The C# code to close the file will NOT be running on the host server.
[DllImport("Netapi32.dll", SetLastError=true, CharSet = CharSet.Unicode)]
public static extern int NetFileClose(string servername, int id);
You can wrap the NetFileClose API, which also requires wrapping the NetFileEnum API. Something like this:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct FILE_INFO_3 {
public int fi3_id;
public int fi3_permissions;
public int fi3_num_locks;
public string fi3_pathname;
public string fi3_username;
}
static class NativeMethods {
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
public extern static int NetFileEnum(
string servername,
string basepath,
string username,
int level,
out IntPtr bufptr,
int prefmaxlen,
out int entriesread,
out int totalentries,
ref IntPtr resume_handle
);
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
public extern static int NetFileClose(string servername, int fileid);
[DllImport("netapi32.dll")]
public extern static int NetApiBufferFree(IntPtr buffer);
}
Pretty ugly signatures, right? Let's wrap a little managed love around it.
class RemoteFile {
public RemoteFile(string serverName, int id, string path, string userName) {
ServerName = serverName;
Id = id;
Path = path;
UserName = userName;
}
public string ServerName { get; }
public int Id { get; }
public string Path { get; }
public string UserName { get; }
public void Close() {
int result = NativeMethods.NetFileClose(ServerName, Id);
if (result != 0) {
// handle error decently, omitted for laziness
throw new Exception($"Error: {result}");
}
}
}
IEnumerable<RemoteFile> EnumRemoteFiles(string serverName, string basePath = null) {
int entriesRead;
int totalEntries;
IntPtr resumeHandle = IntPtr.Zero;
IntPtr fileEntriesPtr = IntPtr.Zero;
try {
int result = NativeMethods.NetFileEnum(
servername: serverName,
basepath: basePath,
username: null,
level: 3,
bufptr: out fileEntriesPtr,
prefmaxlen: -1,
entriesread: out entriesRead,
totalentries: out totalEntries,
resume_handle: ref resumeHandle
);
if (result != 0) {
// handle error decently, omitted for laziness
throw new Exception($"Error: {result}");
}
for (int i = 0; i != entriesRead; ++i) {
FILE_INFO_3 fileInfo = (FILE_INFO_3) Marshal.PtrToStructure(
fileEntriesPtr + i * Marshal.SizeOf(typeof(FILE_INFO_3)),
typeof(FILE_INFO_3)
);
yield return new RemoteFile(
serverName,
fileInfo.fi3_id,
fileInfo.fi3_pathname,
fileInfo.fi3_username
);
}
} finally {
if (fileEntriesPtr != IntPtr.Zero) {
NativeMethods.NetApiBufferFree(fileEntriesPtr);
}
}
}
And now closing a particular file is easy: close all open instances of it.
foreach (var file in EnumRemoteFiles(server, path)) {
Console.WriteLine($"Closing {file.Path} at {file.ServerName} (opened by {file.UserName})");
file.Close();
}
Please note that this code is not quite production ready, in particular, the error handling sucks. Also, in my tests, it appears file paths can be subject to some mangling, depending on exactly how they're opened (like a file appearing as C:\\Path\File, with an extra backslash after the drive root) so you may want to do normalization before validating the name. Still, this covers the ground.
Hello i want to set IE proxy using a C# program as WebProxy class have get proxy method.But there is no method to set it!
Here are some alternatives found by Googling:
1- GlobalProxySelection
This is from http://www.hccp.org/csharp-http-proxy.html
Sample Code:
System.Net.Uri proxyURI = new System.Net.Uri("http://64.202.165.130:3128");
System.Net.GlobalProxySelection.Select = new System.Net.WebProxy(proxyURI);
2- Another discussion on StackOverflow: Programmatically Set Browser Proxy Settings in C#
Read it if you want the proxy only for your app.
For global change, it suggests looking at: http://msdn.microsoft.com/en-us/library/aa384113.aspx
Sample Code:
WINHTTP_PROXY_INFO proxyInfo;
// Allocate memory for string members.
proxyInfo.lpszProxy = new WCHAR[25];
proxyInfo.lpszProxyBypass = new WCHAR[25];
// Set the members of the proxy info structure.
proxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
swprintf_s(proxyInfo.lpszProxy, 25, L"proxy_server");
swprintf_s(proxyInfo.lpszProxyBypass, 25, L"<local>");
// Set the default proxy configuration.
if (WinHttpSetDefaultProxyConfiguration( &proxyInfo ))
printf("Proxy Configuration Set.\n");
// Free memory allocated to the strings.
delete [] proxyInfo.lpszProxy;
delete [] proxyInfo.lpszProxyBypass;
3- Using Native Code
This is from http://huddledmasses.org/setting-windows-internet-connection-proxy-from-c/
Sample Code:
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace PoshHttp
{
public class Proxies
{
public static bool UnsetProxy()
{
return SetProxy(null, null);
}
public static bool SetProxy(string strProxy)
{
return SetProxy(strProxy, null);
}
public static bool SetProxy(string strProxy, string exceptions)
{
InternetPerConnOptionList list = new InternetPerConnOptionList();
int optionCount = string.IsNullOrEmpty(strProxy) ? 1 : (string.IsNullOrEmpty(exceptions) ? 2 : 3);
InternetConnectionOption[] options = new InternetConnectionOption[optionCount];
// USE a proxy server ...
options[0].m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS;
options[0].m_Value.m_Int = (int)((optionCount < 2) ? PerConnFlags.PROXY_TYPE_DIRECT : (PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_PROXY));
// use THIS proxy server
if (optionCount > 1)
{
options[1].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_SERVER;
options[1].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(strProxy);
// except for these addresses ...
if (optionCount > 2)
{
options[2].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_BYPASS;
options[2].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(exceptions);
}
}
// default stuff
list.dwSize = Marshal.SizeOf(list);
list.szConnection = IntPtr.Zero;
list.dwOptionCount = options.Length;
list.dwOptionError = 0;
int optSize = Marshal.SizeOf(typeof(InternetConnectionOption));
// make a pointer out of all that ...
IntPtr optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length);
// copy the array over into that spot in memory ...
for (int i = 0; i < options.Length; ++i)
{
IntPtr opt = new IntPtr(optionsPtr.ToInt32() + (i * optSize));
Marshal.StructureToPtr(options[i], opt, false);
}
list.options = optionsPtr;
// and then make a pointer out of the whole list
IntPtr ipcoListPtr = Marshal.AllocCoTaskMem((Int32)list.dwSize);
Marshal.StructureToPtr(list, ipcoListPtr, false);
// and finally, call the API method!
int returnvalue = NativeMethods.InternetSetOption(IntPtr.Zero,
InternetOption.INTERNET_OPTION_PER_CONNECTION_OPTION,
ipcoListPtr, list.dwSize) ? -1 : 0;
if (returnvalue == 0)
{ // get the error codes, they might be helpful
returnvalue = Marshal.GetLastWin32Error();
}
// FREE the data ASAP
Marshal.FreeCoTaskMem(optionsPtr);
Marshal.FreeCoTaskMem(ipcoListPtr);
if (returnvalue > 0)
{ // throw the error codes, they might be helpful
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return (returnvalue < 0);
}
}
#region WinInet structures
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct InternetPerConnOptionList
{
public int dwSize; // size of the INTERNET_PER_CONN_OPTION_LIST struct
public IntPtr szConnection; // connection name to set/query options
public int dwOptionCount; // number of options to set/query
public int dwOptionError; // on error, which option failed
//[MarshalAs(UnmanagedType.)]
public IntPtr options;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct InternetConnectionOption
{
static readonly int Size;
public PerConnOption m_Option;
public InternetConnectionOptionValue m_Value;
static InternetConnectionOption()
{
InternetConnectionOption.Size = Marshal.SizeOf(typeof(InternetConnectionOption));
}
// Nested Types
[StructLayout(LayoutKind.Explicit)]
public struct InternetConnectionOptionValue
{
// Fields
[FieldOffset(0)]
public System.Runtime.InteropServices.ComTypes.FILETIME m_FileTime;
[FieldOffset(0)]
public int m_Int;
[FieldOffset(0)]
public IntPtr m_StringPtr;
}
}
#endregion
#region WinInet enums
//
// options manifests for Internet{Query|Set}Option
//
public enum InternetOption : uint
{
INTERNET_OPTION_PER_CONNECTION_OPTION = 75
}
//
// Options used in INTERNET_PER_CONN_OPTON struct
//
public enum PerConnOption
{
INTERNET_PER_CONN_FLAGS = 1, // Sets or retrieves the connection type. The Value member will contain one or more of the values from PerConnFlags
INTERNET_PER_CONN_PROXY_SERVER = 2, // Sets or retrieves a string containing the proxy servers.
INTERNET_PER_CONN_PROXY_BYPASS = 3, // Sets or retrieves a string containing the URLs that do not use the proxy server.
INTERNET_PER_CONN_AUTOCONFIG_URL = 4//, // Sets or retrieves a string containing the URL to the automatic configuration script.
}
//
// PER_CONN_FLAGS
//
[Flags]
public enum PerConnFlags
{
PROXY_TYPE_DIRECT = 0x00000001, // direct to net
PROXY_TYPE_PROXY = 0x00000002, // via named proxy
PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy URL
PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection
}
#endregion
internal static class NativeMethods
{
[DllImport("WinInet.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool InternetSetOption(IntPtr hInternet, InternetOption dwOption, IntPtr lpBuffer, int dwBufferLength);
}
}
Please check the links themselves for details and complete parts of the solutions.
I am trying to remove a large number of files from a location (by large I mean over 100000), whereby the action is initated from a web page. Obviously I could just use
string[] files = System.IO.Directory.GetFiles("path with files to delete");
foreach (var file in files) {
IO.File.Delete(file);
}
Directory.GetFiles
http://msdn.microsoft.com/en-us/library/wz42302f.aspx
This method has already been posted a few times:
How to delete all files and folders in a directory?
and
Delete files from directory if filename contains a certain word
But the problem with this method is that if you have say a hundred thousand files it becomes a performance issue as it has to generate all of the filepaths first before looping through them.
Added to this if a web page is waiting a response from a method which is performing this as you can imagine it will look a bit rubbish!
One thought I had was to wrap this up in an an asychrnonous web service call and when it completes it fires back a response to the web page to say that they have been removed? Maybe put the delete method in a separate thread? Or maybe even use a seperate batch process to perform the delete?
I have a similar issue when trying to count the number of files in a directory - if it contains a large number of files.
I was wondering if this is all a bit overkill? I.e. is there a simpler method to deal with this? Any help would be appreciated.
GetFiles is extremely slow.
If you are invoking it from a website, you might just throw a new Thread which does this trick.
An ASP.NET AJAX call that returns whether there are still matching files, can be used to do basic progress updates.
Below an implementation of a fast Win32 wrapping for GetFiles, use it in combination with a new Thread and an AJAX function like: GetFilesUnmanaged(#"C:\myDir", "*.txt*).GetEnumerator().MoveNext().
Usage
Thread workerThread = new Thread(new ThreadStart((MethodInvoker)(()=>
{
foreach(var file in GetFilesUnmanaged(#"C:\myDir", "*.txt"))
File.Delete(file);
})));
workerThread.Start();
//just go on with your normal requests, the directory will be cleaned while the user can just surf around
public static IEnumerable<string> GetFilesUnmanaged(string directory, string filter)
{
return new FilesFinder(Path.Combine(directory, filter))
.Where(f => (f.Attributes & FileAttributes.Normal) == FileAttributes.Normal
|| (f.Attributes & FileAttributes.Archive) == FileAttributes.Archive)
.Select(s => s.FileName);
}
}
public class FilesEnumerator : IEnumerator<FoundFileData>
{
#region Interop imports
private const int ERROR_FILE_NOT_FOUND = 2;
private const int ERROR_NO_MORE_FILES = 18;
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool FindNextFile(SafeHandle hFindFile, out WIN32_FIND_DATA lpFindFileData);
#endregion
#region Data Members
private readonly string _fileName;
private SafeHandle _findHandle;
private WIN32_FIND_DATA _win32FindData;
#endregion
public FilesEnumerator(string fileName)
{
_fileName = fileName;
_findHandle = null;
_win32FindData = new WIN32_FIND_DATA();
}
#region IEnumerator<FoundFileData> Members
public FoundFileData Current
{
get
{
if (_findHandle == null)
throw new InvalidOperationException("MoveNext() must be called first");
return new FoundFileData(ref _win32FindData);
}
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
if (_findHandle == null)
{
_findHandle = new SafeFileHandle(FindFirstFile(_fileName, out _win32FindData), true);
if (_findHandle.IsInvalid)
{
int lastError = Marshal.GetLastWin32Error();
if (lastError == ERROR_FILE_NOT_FOUND)
return false;
throw new Win32Exception(lastError);
}
}
else
{
if (!FindNextFile(_findHandle, out _win32FindData))
{
int lastError = Marshal.GetLastWin32Error();
if (lastError == ERROR_NO_MORE_FILES)
return false;
throw new Win32Exception(lastError);
}
}
return true;
}
public void Reset()
{
if (_findHandle.IsInvalid)
return;
_findHandle.Close();
_findHandle.SetHandleAsInvalid();
}
public void Dispose()
{
_findHandle.Dispose();
}
#endregion
}
public class FilesFinder : IEnumerable<FoundFileData>
{
readonly string _fileName;
public FilesFinder(string fileName)
{
_fileName = fileName;
}
public IEnumerator<FoundFileData> GetEnumerator()
{
return new FilesEnumerator(_fileName);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class FoundFileData
{
public string AlternateFileName;
public FileAttributes Attributes;
public DateTime CreationTime;
public string FileName;
public DateTime LastAccessTime;
public DateTime LastWriteTime;
public UInt64 Size;
internal FoundFileData(ref WIN32_FIND_DATA win32FindData)
{
Attributes = (FileAttributes)win32FindData.dwFileAttributes;
CreationTime = DateTime.FromFileTime((long)
(((UInt64)win32FindData.ftCreationTime.dwHighDateTime << 32) +
(UInt64)win32FindData.ftCreationTime.dwLowDateTime));
LastAccessTime = DateTime.FromFileTime((long)
(((UInt64)win32FindData.ftLastAccessTime.dwHighDateTime << 32) +
(UInt64)win32FindData.ftLastAccessTime.dwLowDateTime));
LastWriteTime = DateTime.FromFileTime((long)
(((UInt64)win32FindData.ftLastWriteTime.dwHighDateTime << 32) +
(UInt64)win32FindData.ftLastWriteTime.dwLowDateTime));
Size = ((UInt64)win32FindData.nFileSizeHigh << 32) + win32FindData.nFileSizeLow;
FileName = win32FindData.cFileName;
AlternateFileName = win32FindData.cAlternateFileName;
}
}
/// <summary>
/// Safely wraps handles that need to be closed via FindClose() WIN32 method (obtained by FindFirstFile())
/// </summary>
public class SafeFindFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FindClose(SafeHandle hFindFile);
public SafeFindFileHandle(bool ownsHandle)
: base(ownsHandle)
{
}
protected override bool ReleaseHandle()
{
return FindClose(this);
}
}
// The CharSet must match the CharSet of the corresponding PInvoke signature
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WIN32_FIND_DATA
{
public uint dwFileAttributes;
public FILETIME ftCreationTime;
public FILETIME ftLastAccessTime;
public FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
Can you put all your files in the same directory?
If so, why don't you just call Directory.Delete(string,bool) on the subdir you want to delete?
If you've already got a list of file paths you want to get rid of, you might actually get better results by moving them to a temp dir then deleting them rather than deleting each file manually.
Cheers,
Florian
Having more than 1000 files in a directory is a huge problem.
If you are in the development stages now, you should consider putting in an algo which will put the files into a random folder (inside your root folder) with a surety of the number of files in that folder to be under 1024.
Something like
public UserVolumeGenerator()
{
SetNumVolumes((short)100);
SetNumSubVolumes((short)1000);
SetVolumesRoot("/var/myproj/volumes");
}
public String GenerateVolume()
{
int volume = random.nextInt(GetNumVolumes());
int subVolume = random.nextInt(GetNumSubVolumes());
return Integer.toString(volume) + "/" + Integer.toString(subVolume);
}
private static final Random random = new Random(System.currentTimeMillis());
While doing this, also make sure that each time you create a file, add it to a HashMap or list simultaneously (the path). Periodically serialize this using something like JSON.net to the filesystem(integrity’s sake, so that even if your service fails, you can get back the file list from the serialized form).
When you want to clean up the files or query among them, first do a lookup of this HashMap or list and then
act on the file. This is better than System.IO.Directory.GetFiles
Some improvements to speed it up in the back end:
Use Directory.EnumerateFiles(..) : this will iterate through files
without waiting after all files have been retrieved.
Use Parallel.Foreach(..) : this will delete files simultaneously.
It should be faster but apparently the HTTP request would still be timeout with the large number of files so the back end process should be executed in separate worker thread and notify result back to web client after finishing.
Do it in a separate thread, or post a message to a queue (maybe MSMQ?) where another application (maybe a windows service) is subscribed to that queue and performs the commands (i.e. "Delete e:\dir*.txt") in it's own process.
The message should probably just include the folder name. If you use something like NServiceBus and transactional queues, then you can post your message and return immediately as long as the message was posted successfully. If there is a problem actually processing the message, then it'll retry and eventually go on an error queue that you can watch and perform maintenance on.
Boot the work out to a worker thread and then return your response to the user.
I'd flag up a application variable to say that you are doing "the big delete job" to stop running multiple threads doing the same work. You could then poll another page which could give you a progress update of the number of files removed so far too if you wanted to?
Just a query but why so many files?
You could create a simple ajax webmethod in your aspx code behind and call it with javascript.
The best choice (imho) would be to create a seperate process to delete/count the files and check on the progress by polling otherwise you might get problems with browser timeouts.
Wow. I think you are definitely on the right track with having some other service or entity taking care of the delete. In doing so you could also provide methods for tracking the process of the delete and showing the result to the user using asynch javascript.
As others have said putting this in another process is a great idea. You do not want IIS hogging resources using such long running jobs. Another reason for doing so is security. You might not want to give your work process that ability to delete files from the disk.
I know it's old thread but in addition to Jan Jongboom answer I propose similar solution which is quite performant and more universal. My solution was built to quickly remove directory structure in DFS with support for long file names (>255 chars).
The first difference is in DLL import declaration.
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern IntPtr FindFirstFile(string lpFileName, ref WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool FindNextFile(IntPtr hDindFile, ref WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MashalAs(UnmanagedType.Bool]
static extern bool DeleteFile(string lpFileName)
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MashalAs(UnmanagedType.Bool]
static extern bool DeleteDirectory(string lpPathName)
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool FindClose(IntPtr hFindFile);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLAstError = true)]
static extern uint GetFileAttributes(string lpFileName);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLAstError = true)]
static extern bool SetFileAttributes(string lpFileName, uint dwFileAttributes);
WIN32_FIND_DATA structure is also slightly different:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode), Serializable, BestFitMapping(false)]
internal struct WIN32_FIND_DATA
{
internal FileAttributes dwFileAttributes;
internal FILETIME ftCreationTime;
internal FILETIME ftLastAccessTime;
internal FILETIME ftLastWriteTime;
internal int nFileSizeHigh;
internal int nFileSizeLow;
internal int dwReserved0;
internal int dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
internal string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
internal string cAlternative;
}
In order to use long paths the path needs to be prepared as follows:
public void RemoveDirectory(string directoryPath)
{
var path = #"\\?\UNC\" + directoryPath.Trim(#" \/".ToCharArray());
SearchAndDelete(path);
}
and here's the main method:
private void SearchAndDelete(string path)
{
var fd = new WIN32_FIND_DATA();
var found = false;
var handle = IntPtr.Zero;
var invalidHandle = new IntPtr(-1);
var fileAttributeDir = 0x00000010;
var filesToRemove = new List<string>();
try
{
handle = FindFirsFile(path + #"\*", ref fd);
if (handle == invalidHandle) return;
do
{
var current = fd.cFileName;
if (((int)fd.dwFileAttributes & fileAttributeDir) != 0)
{
if (current != "." && current != "..")
{
var newPath = Path.Combine(path, current);
SearchAndDelete(newPath);
}
}
else
{
filesToRemove.Add(Path.Combine(path, current));
}
found = FindNextFile(handle, ref fd);
} while (found);
}
finally
{
FindClose(handle);
}
try
{
object lockSource = new Object();
var exceptions = new List<Exception>();
Parallel.ForEach(filesToRemove, file, =>
{
var attrs = GetFileAttributes(file);
attrs &= ~(uint)0x00000002; // hidden
attrs &= ~(uint)0x00000001; // read-only
SetFileAttributes(file, attrs);
if (!DeleteFile(file))
{
var msg = string.Format("Cannot remove file {0}.{1}{2}", file.Replace(#"\\?\UNC", #"\"), Environment.NewLine, new Win32Exception(Marshal.GetLastWin32Error()).Message);
lock(lockSource)
{
exceptions.Add(new Exceptions(msg));
}
}
});
if (exceptions.Any())
{
throw new AggregateException(exceptions);
}
}
var dirAttr = GetFileAttributes(path);
dirAttr &= ~(uint)0x00000002; // hidden
dirAttr &= ~(uint)0x00000001; // read-only
SetfileAttributtes(path, dirAttr);
if (!RemoveDirectory(path))
{
throw new Exception(new Win32Exception(Marshal.GetLAstWin32Error()));
}
}
of course we could go further and store directories in separate list outside of that method and delete them later in another method which could look like this:
private void DeleteDirectoryTree(List<string> directories)
{
// group directories by depth level and order it by level descending
var data = directories.GroupBy(d => d.Split('\\'),
d => d,
(key, dirs) => new
{
Level = key,
Directories = dirs.ToList()
}).OrderByDescending(l => l.Level);
var exceptions = new List<Exception>();
var lockSource = new Object();
foreach (var level in data)
{
Parallel.ForEach(level.Directories, dir =>
{
var attrs = GetFileAttributes(dir);
attrs &= ~(uint)0x00000002; // hidden
attrs &= ~(uint)0x00000001; // read-only
SetFileAttributes(dir, attrs);
if (!RemoveDirectory(dir))
{
var msg = string.Format("Cannot remove directory {0}.{1}{2}", dir.Replace(#"\\?\UNC\", string.Empty), Environment.NewLine, new Win32Exception(Marshal.GetLastWin32Error()).Message);
lock (lockSource)
{
exceptions.Add(new Exception(msg));
}
}
});
}
if (exceptions.Any())
{
throw new AggregateException(exceptions);
}
}