2

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);
Brendan Gooden
  • 1,460
  • 2
  • 21
  • 40
  • [This question](http://stackoverflow.com/q/10945105/15498) looks at the problem from the other direction - finding ways to avoid locking the files whilst the application is running - perhaps that could be an alternative approach? – Damien_The_Unbeliever Nov 07 '16 at 07:46

1 Answers1

4

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.

Jeroen Mostert
  • 27,176
  • 2
  • 52
  • 85
  • Perfect, this is what I was looking for. Also it works well with impersonation so i'ts very useful (I used Minimod.Impersonator) – Keytrap Aug 21 '20 at 09:59