views:

218

answers:

5

This is a client server application. I'm creating a update program that will replace a list of exe files, run scripts, and anything else that needs to be updated. This will be installed on the server.

First I need to check if the executable file is opened via a network share. I can do this manually by going into Computer Management then Shared files and Open files. This seems to be the only way to check if the file is open. I tried using R/W to check if the file is opened but this did not work. Looked at Win32_ServerConnection but this just listed number of files that were open not the names.

I would like to code this in Delphi 7 or C# if it can't be done in Delphi. I have found a few programs that can view the open files on a server but nothing on how this can be done.

A: 

Have you seen the FileSystemWatcher in System.IO?

http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.aspx

TJMonk15
How does that help? An opened executable is not a file system modification and will therefore not be reported by the file system watcher, or am I wrong here?
Smasher
Bugger. I thought it could handle file accesses as well. Sorry.
TJMonk15
+2  A: 

Why don't you just try to delete it? You will get an error if the file is currently open. And if deleting it works, you can just copy the updated executable.

Smasher
There should be another way to check if a is getting accessed from the server. You can do it visually. I would think there is a way in code to do it.
Jason
+2  A: 

You could keep a static list of "servers" that this will run on, you can lookup the process machine name, hostname, or computer name for a match. If its in the list you can assume its open local.

string name = Environment.MachineName;
string name = System.Net.Dns.GetHostName();
string name = System.Windows.Forms.SystemInformation.ComputerName;

Process[] myProcesses = Process.GetProcessesByName("myExeProcName",MachineName);
foreach(Process myProcess in myProcesses)
{
    Console.Write("MachineName : " + myProcess.MachineName + "\n");
}
Tj Kellie
Doesn't this need administrator privileges?
Brave Cobra
+1  A: 

On Windows 2000 and later, you could use NetFileEnum API:

program test;

{$APPTYPE CONSOLE}

uses
  Windows,
  Classes,
  SysUtils;

type
  PFileInfo3 = ^TFileInfo3;
  TFileInfo3 = record
    fi3_id: DWORD;
    fi3_permissions: DWORD;
    fi3_num_locks: DWORD;
    fi3_pathname: PWideChar;
    fi3_username: PWideChar;
  end;

  TNetFileEnumCallback = function(const FileInfo: TFileInfo3; Data: Pointer): Boolean;

const
  netapi = 'netapi32.dll';

function NetApiBufferFree(Buffer: Pointer): DWORD; stdcall; external netapi;
function NetFileEnum(servername: PWideChar; basepath: PWideChar; username: PWideChar; level: DWORD; var bufptr: Pointer;
  prefmaxlen: DWORD; var entriesread: DWORD; var totalentries: DWORD; resume_handle: PDWORD): DWORD; stdcall;
  external netapi;

procedure EnumNetFileUsers(const LocalPath: WideString; Callback: TNetFileEnumCallback; Data: Pointer = nil);
const
  EntryCount = 32;
var
  NetResult, EntriesRead, TotalEntries, ResumeHandle: Cardinal;
  Buf: Pointer;
  P: PFileInfo3;
  I: Integer;
begin
  EntriesRead := 0;               
  TotalEntries := 0;
  ResumeHandle := 0;
  repeat
    NetResult := NetFileEnum(nil, PWideChar(LocalPath), nil, 3, Buf, EntryCount * SizeOf(TFileInfo3), EntriesRead,
      TotalEntries, @ResumeHandle);
    if not (NetResult in [ERROR_SUCCESS, ERROR_MORE_DATA]) then
      RaiseLastOSError(NetResult);
    try
      P := Buf;
      for I := 0 to EntriesRead - 1 do
      begin
        if Callback(P^, Data) then
          Break;

        Inc(P);
      end;
    finally
      NetApiBufferFree(Buf);
    end;
  until NetResult = ERROR_SUCCESS;
end;

function ShowFileInfo(const FileInfo: TFileInfo3; Data: Pointer): Boolean;
begin
  Result := False;
  Writeln(Format('id: %d, permissions: $%.8x, num_locks: %d, pathname: ''%s'', username: ''%s''',
    [FileInfo.fi3_id, FileInfo.fi3_permissions, FileInfo.fi3_num_locks, FileInfo.fi3_pathname, FileInfo.fi3_username]));
end;

begin
  try
    if ParamCount = 1 then
      EnumNetFileUsers(ParamStr(1), ShowFileInfo);
  except
    on E: Exception do
    begin
      Writeln(Format('[%s] %s', [E.ClassName, E.Message]));
      ExitCode := 1;
    end;
  end;
end.

It seems that the path passed in the LocalPath parameter has to begin with a double backslash, e.g.:

test.exe C:\\Dev\Test\test.exe

produces the following output (if the file is open through a share):

id: -469761405, permissions: $00000001, num_locks: 0, pathname: 'C:\\Dev\Test\test.exe', username: 'Ondrej'

Also note that MSDN says:

"Only members of the Administrators or Server Operators local group can successfully execute the NetFileEnum function."

TOndrej
You need administrator privileges for this to work.
Brave Cobra
but still useful code, nevertheless! ;)
Brave Cobra
Thanks, I've added a note about that.
TOndrej
A: 

I use a function like this to check whether I can alter a file on the filesystem. Basically I try to "create" a new file called fName, still opening the existing (should it exist) and get a valid file handle to it. If that fails, then the file is in use. The function does NOT actually create a file, nor does it alter the existing filessytem (I never do anything with the handle). It simply check whether I can get a file Handle to the file, should I want to do something with it.

This also works for files being opened from a share on a remote computer.

 function IsFileInUse(fName: string): boolean;
  var
    HFileRes: HFILE;
  begin
    Result := false;
    if not FileExists(fName) then
      exit;
    HFileRes := CreateFile(pchar(fName),
      GENERIC_READ or GENERIC_WRITE,
      0, nil, OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,
      0);
    Result := (HFileRes = INVALID_HANDLE_VALUE);
    if not Result then
      CloseHandle(HFileRes);
  end;
Brave Cobra
This did what I needed it to do. Thanks
Jason
You're welcome!
Brave Cobra