Who has files open on the network

topic: 

The WhoHasFileOpen

utility displays the list of users who have specific file(s) open on network. Works on Windows NT Platform (Windows NT 4.0, Windows 2000, etc). This code detects only the files opened using a net shared path. It does not return the files opened by a user on the local computer using a local path.

The code is based on Ramon F. Jaquez's UT FAQ #7896 Who opened what files on the network? (modified to use only VFP code)

.

Thanks Kevin Delaney for code cleanup which made possible to post it here.

The code uses Windows API support class

.

A user running this program must be a member of the Administrators or Account Operators local group on the file server where files are located

.

This is sample code. Add error handling and adjust to your requirements as necessary.


Examples
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

* Mapped drive, specific file 'Sometable.dbf'
WhoHasFileOpen("X:\Somefolder\SumSubfolder\Sometable.dbf")

* Mapped drive, all extensions for specific file name 'Sometable'
WhoHasFileOpen("X:\Somefolder\SomeSubfolder\Sometable")

* UNC path, specific file 'Sometable.dbf'
WhoHasFileOpen("\\SomeServer\SomeShare\Somefolder\SomemSubfolder\Sometable.dbf")

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
*
* WhoHasFileOpen.prg
*
*-- For Windows NT Platform (NT 4, NT 2000, e.t.c)
*  
* Based of Ramon F. Jaquez UT FAQ  # 7896
* Who opened what files on the network? (modified to use only VFP code)
*
*--
* The following program displays the open files, the users that
* opened these files and other related information.
*
* This code detects only the files opened using a net shared
* path. It does not return the files opened by a user on the
* local computer using a local path (i.e. the computer where
* the user is logged on). This is normal, because, otherwise,
* the number of returned files would be huge.
*
* The user running this program must be a member of the
* Administrators or Account Operators local group.
*
* In order to keep the code simple, the error handling only
* displays the error code. You should integrate it in your
* error handling mechanism.
*
*-- This function returns information about open files.
*   It returns the open files only if they were
*   opened using a share on that computer.
*
*-- It uses:
*      - The NetFileEnum Win32 API function to retrieve the wanted information from the OS.
*
*-- Parameters:
*      1. The full file name including path. An extension can be ommited.

LPARAMETERS tcFileName
LOCAL lcDriveLetter, lcFileMask, llMask, lcRestName

#DEFINE PERM_FILE_READ      0x1 && user has read access
#DEFINE PERM_FILE_WRITE     0x2 && user has write access
#DEFINE PERM_FILE_CREATE    0x4 && user has create access


#DEFINE ACCESS_READ         0x01
#DEFINE ACCESS_WRITE        0x02
#DEFINE ACCESS_CREATE       0x04
#DEFINE ACCESS_EXEC         0x08
#DEFINE ACCESS_DELETE       0x10
#DEFINE ACCESS_ATRIB        0x20
#DEFINE ACCESS_PERM         0x40

#DEFINE ACCESS_GROUP        0x8000

#DEFINE ACCESS_ALL          ( ACCESS_READ + ACCESS_WRITE + ACCESS_CREATE + ;
                                ACCESS_EXEC + ACCESS_DELETE + ACCESS_ATRIB + ACCESS_PERM )

LOCAL lcServerName, lcBasePath, lcUserName, lnBufferPointer
LOCAL lnPreferedMaxLength, lnEntriesRead, lnTotalEntries
LOCAL lnResumeHandle, lnError, loPointersObject
LOCAL lnI, lcDll, lnPermissions, lnID
LOCAL llContinue, lnpFileInfo, lcFileName
LOCAL lnLocks, loRec, lcPermissions, lcServerNameUC, lcBasePathUC, lcUserNameUC

IF ("?" $ tcFileName) OR ("*" $ tcFileName) 
	_msgbox("File Mask is not supported.")
	RETURN
ENDIF

IF EMPTY(SYS(2000, DEFAULTEXT(tcFileName,"*")))
	_msgbox("File Name '" + tcFileName + "' not found")
	RETURN
ENDIF

IF LEFT(tcFileName,2) = "\\"
	lcNetName = LEFT(tcFileName, AT("\", tcFileName, 4)-1)
	lcRestName = SUBSTR(tcFileName, AT("\", tcFileName, 4)+1)
	lcDriveLetter = lcNetName
ELSE
	lcDriveLetter = UPPER(JUSTDRIVE(tcFileName))
	IF EMPTY(lcDriveLetter)
		_msgbox("Incorrect File Name '" + tcFileName + "'")
		RETURN
	ENDIF	

	* Convert a driver letter to the UNC path
	lcNetName = _LocalName2UNC(lcDriveLetter)
	IF EMPTY(lcNetName)
		_msgbox(lcDriveLetter + " isn't a network drive - '" + tcFileName + "'")
		RETURN
	ENDIF	
	lcRestName = SUBSTR(JUSTPATH(tcFileName),4)
ENDIF	

* Convert share UNC path to the server local path
lcServerName = "\\" + STREXTRACT(lcNetName, "\\", "\")
lcLocalPath = _Share2LocalPath(lcNetName)

IF ISNULL(lcLocalPath)
	RETURN
ENDIF

lcBasePath = ADDBS(lcLocalPath) + lcRestName
lcUserName = ""
lcFileMask = JUSTFNAME(tcFileName)

DECLARE INTEGER NetFileEnum IN NETAPI32              ;
	STRING  @ServerName, STRING  @BasePath,            ;
	STRING  @UserName, INTEGER nLevel,                 ;
	INTEGER  @BufferPointer, INTEGER PreferedMaxLength, ;
	INTEGER @EntriesRead, INTEGER @TotalEntries,       ;
	INTEGER @ResumeHandle

*-- This is the structure used by NetFileEnum to retrieve the information.
*typedef struct _FILE_INFO_3 {
* DWORD fi3_id;
* DWORD fi3_permissions;
* DWORD fi3_num_locks;
* LPWSTR fi3_pathname;
* LPWSTR fi3_username;} FILE_INFO_3

loWas = NEWOBJECT("WinApiSupport", "WinApiSupport.fxp")

CREATE CURSOR crsWhoHas ( ;
		UserName C(10), ;
		Locks I, ;
		FileID I, ;
		Permissions C(24), ;
		FileName C(254), ;
		ServerFileName C(254))

SCATTER MEMO NAME loRec	

*-- The server name, the base path and the user name must be in Unicode format.
lcServerNameUC = StrConv(StrConv(lcServerName + Chr(0), 1), 5)
lcBasePathUC   = StrConv(StrConv(lcBasePath + Chr(0), 1), 5)
lcUserNameUC   = StrConv(StrConv(lcUserName + Chr(0), 1), 5)


*-- Allow for a very large buffer.
*   If this length is not enough, the info
*   will be retrieved in more than one step.
lnPreferedMaxLength = 100000000

lnResumeHandle  = 0
lnEntriesRead   = 0
lnTotalEntries  = 0
lnBufferPointer = 0

llContinue = .t.
DO WHILE llContinue
	lnError = NetFileEnum( lcServerNameUC, lcBasePathUC, lcUserNameUC, 3, ;
					@lnBufferPointer, lnPreferedMaxLength, @lnEntriesRead, ;
					@lnTotalEntries, @lnResumeHandle)
	IF lnEntriesRead = 0
		*-- There are no (more) open files.
		llContinue = .f.
	ENDIF
	IF lnError = 0
		FOR lnI = 1 TO lnEntriesRead
			lnpFileInfo = lnBufferPointer + (lnI - 1) * 20
			lcFileName = loWas.StrZFromBufferW(lnpFileInfo + 12)
			IF UPPER(JUSTFNAME(lcFileName)) <> UPPER(lcFileMask)
				LOOP
			ENDIF
			lnpFileInfo = lnBufferPointer + (lnI - 1) * 20

			*-- Extract the file name
			loRec.FileName 		= lcDriveLetter + "\" + STREXTRACT(lcFileName, lcLocalPath, "",1,1)
			loRec.ServerFileName = lcFileName
			
			*-- Extract the number of locks on this file
			lnLocks = loWas.Long2NumFromBuffer(lnpFileInfo + 8)
			loRec.Locks = lnLocks

			*-- Extract the user name that opened the file
			lcUserName = loWas.StrZFromBufferW(lnpFileInfo + 16)
			
			loRec.UserName = lcUserName

			*-- Extract the permissions on this file
			lnPermissions = loWas.Long2NumFromBuffer( lnpFileInfo + 4)

			lcPermissions = ""
			IF BITAND(lnPermissions, PERM_FILE_READ) > 0
				lcPermissions = lcPermissions + "Read+"
			ENDIF
			IF BITAND(lnPermissions, PERM_FILE_WRITE) > 0
				lcPermissions = lcPermissions + "Write+"
			ENDIF
			IF BITAND(lnPermissions, PERM_FILE_CREATE) > 0
				lcPermissions = lcPermissions + "Create+"
			ENDIF

			loRec.Permissions = LEFT(lcPermissions, LEN(lcPermissions)-1)

			*-- Extract the ID for this file.
			*   This ID is generated when the file is opened and
			*   can be used as parameter for the NetFileGetInfo
			*   Win32 API function.
			lnID = loWas.Long2NumFromBuffer(lnpFileInfo)
			loRec.FileID = lnID
			
			INSERT INTO crsWhoHas FROM NAME loRec
		ENDFOR

		*-- Free the memory allocated by NetFileEnum
		IF lnBufferPointer <> 0
			DeAllocNetAPIBuffer(lnBufferPointer)
		ENDIF
	ELSE
		_msgbox("NetFileEnum: Error " + _apierror(lnError))
		llContinue = .f.
	ENDIF
ENDDO

IF RECCOUNT("crsWhoHas") = 0
	_msgbox("No open files found for '" + tcFileName + "'")
	RETURN
ENDIF
SELECT crsWhoHas
INDEX ON UserName TAG UserName
LOCATE
BROWSE LAST NOWAIT NAME oBr
oBr.ReadOnly = .T.
oBr.Columns(1).Header1.Caption = "User Name"
oBr.Columns(3).Header1.Caption = "File ID"
oBr.Columns(5).Header1.Caption = "File Name"
oBr.Columns(6).Header1.Caption = "Server File Name"
oBr.AutoFit()

RETURN
*----------------------------------------------------------------------------------

PROCEDURE _apierror
	LPARAMETERS tnErrorCode
	LOCAL lcErrBuffer, lcErrorMess, lnNewErr
	DECLARE Long FormatMessage In kernel32.dll ;
				Long dwFlags, String @lpSource, ;
				Long dwMessageId, Long dwLanguageId, ;
				String @lpBuffer, Long nSize, Long Arguments

	lcErrBuffer = REPL(CHR(0),1000)
	lnNewErr = FormatMessage(0x1000,.NULL., tnErrorCode, 0, @lcErrBuffer,500,0)

	lcErrorMess = TRANSFORM(tnErrorCode) + "    " + LEFT(lcErrBuffer, AT(CHR(0),lcErrBuffer)- 1 )

RETURN lcErrorMess

PROCEDURE _msgbox
	LPARAMETERS tcMessage
	=MESSAGEBOX(tcMessage,16)
RETURN "OK"

PROCEDURE _share2localpath
	LPARAMETERS tcNetName
	LOCAL loWas, lnBufferPointer, lcServer, lcShare, lnRC, lcPathRest, loWas, lcLocalPath

	IF EMPTY(tcNetName) OR TYPE("tcNetName") <> "C"
		ERROR 11
	ENDIF

	DECLARE Long NetShareGetInfo IN Netapi32.dll ;
	  String servername, String netname, Long level, Long @bufptr

	lcServer = STREXTRACT(tcNetName, "\\", "\")
	IF EMPTY(lcServer)
		RETURN ""
	ENDIF	

	lcShare = STREXTRACT(tcNetName, "\\" + lcServer + "\", "\",1,1+2)
	lcPathRest = STREXTRACT(tcNetName, "\\" + lcServer + "\" + lcShare + "\", "",1,1)
	IF EMPTY(lcShare)
		RETURN ""
	ENDIF	

	lnBufferPointer = 0
	lnRC = NetShareGetInfo(STRCONV(lcServer+CHR(0),5), STRCONV(lcShare+CHR(0),5), 2, @lnBufferPointer) 
	IF lnRC = 0
		loWas = NEWOBJECT("WinApiSupport", "WinApiSupport.fxp")
		lcLocalPath = ADDBS(loWas.strzfrombufferw(lnBufferPointer + 24)) + lcPathRest
	ELSE
		lcLocalPath = Null
		_msgbox("NetShareGetInfo: Error accessing server '" + lcServer + "', share '" + lcShare + "'"  + CHR(13) + _apierror(lnRC))
	ENDIF	
	*!*	typedef struct _SHARE_INFO_2 {  
	*!*	 0	LPWSTR shi2_netname;  
	*!*	 4	DWORD shi2_type;  
	*!*	 8	LPWSTR shi2_remark;  
	*!*	12	DWORD shi2_permissions;  
	*!*	16	DWORD shi2_max_uses;  
	*!*	20	DWORD shi2_current_uses;  
	*!*	24	LPWSTR shi2_path;  
	*!*	28	LPWSTR shi2_passwd;
	*!*	} SHARE_INFO_2
RETURN 	lcLocalPath

PROCEDURE _LocalName2UNC
	PARAMETERS tcLocalName
	LOCAL lcUNCBuffer, lnLength, lcLocalName
	DECLARE INTEGER WNetGetConnection IN WIN32API ;
	   STRING @ lpLocalName, ;
	   STRING @ lpRemoteName, ;
	   INTEGER @ lplnLength
	   
	IF EMPTY(tcLocalName) OR TYPE("tcLocalName") <> "C"
		ERROR 11
	ENDIF

	lcLocalName = ALLTRIM(tcLocalName)

	IF LEN(lcLocalName) = 1
		lcLocalName = lcLocalName + ":"
	ENDIF
	lcUNCBuffer = REPL(CHR(0),261)
	lnLength = LEN(lcUNCBuffer)
	IF WNetGetConnection(lcLocalName, @lcUNCBuffer, @lnLength) = 0
	   lcRemoteName = LEFT(lcUNCBuffer,AT(CHR(0),lcUNCBuffer)-1)
	ELSE   
		lcRemoteName = ""
	ENDIF

RETURN lcRemoteName 

FUNCTION DeAllocNetAPIBuffer
	*
	*	Frees the NetAPIBuffer allocated at the address specified by nPtr.
	*	The API call is not supported under Win9x
	LPARAMETER tnBufferPointer
	DECLARE INTEGER NetApiBufferFree IN NETAPI32.DLL ;
			INTEGER lpBuffer
RETURN (NetApiBufferFree(INT(tnBufferPointer)) = 0)

Comments

Thank you!
It's what I need.

Hello,

Pls see the line with the _msgbox(...,64, Unable to continue), there seems to be too many parameters

I changed it to messagebox(), but always get error 5 from netfileenum (admin, file exists on a netshare, a dbf opened by me)

What am I doing wrong ?

BTW : The parameters in the debugger all have one space too much at the end, eg servername "server " while its "server"

Thanks and best regards
tom

Hi Tom,

The Error 5 is 'Access is denied - The user does not have access to the requested information'. Something is not right with your permissions.

If there was a space at the end of server name, you would get error 123 not 5.

I fixed _msgbox problem an uploaded new version. Thanks.

Hi Sergey
Could you help - the WhoHasFileOpen.zip file has gone!
I would be very grateful if you could you send it to me please?
DDSquid@hotmail.com

You can download it now

Hi
I downloaded the file NOW and I get messagebox 'netsharegetinfoerror acceses ... server(myserver name) share..'.
I get the message even I open this table from vfp and I try 'WhoHasFileOpen' AS FOLLOWING:
1
2
3
4
5
6
 
use q:\data\dorusers.dbf shared
*c:\aaa\=WhoHasFileOpen prg files PATH
cd c:\aaa
set path to c:\aaa
whohasfileopen("Q:\DATA\DORUSERS.dbf")

Thanks

The user running this program must be a member of the Administrators or Account Operators local group on the server.

I have access the table by windows explorer ,by unc and by IP address, in addition I have the server username&password.
Can I access by code or by RunAS from my local?
Thanks

The program does not access the file (table) but queries the server about open files. That's why it requires the user to be a member of the Administrators or Account Operators local group on the server. It should work if you use RunAS with login that is a member of such group.

It is exactly function need, for years, but new future version is view two type data in cursor table or return only the number user and / or the user in array result. i work for this.
Muchas Gracias y disculpe mi ingles. (very tank and sorry for my poor inglish)

I have been looking into how to accomplish - you have a very elegant and useful solution.

Thank you very much for both your work and that you have decided to share your efforts.

Hi,
I am running this like this
whohasfileopen("S:\PRO50\APEX04\POTRAN04.dbf")
where S: drive is the letter mapped to the shared folder name "Accounting" and in that folder are folders "PRO50" and "APEX04", when i run the above command from the command window, i get this " function, argument value, type, or count is invalid", after i press "ignore" then i get the messagebox with "FILE MASK IS NOT SUPPORTED" , i am running this from VFP 9.0 SPK2, Please advise thanks

You have source code. What else do you need to figure out what is the problem?

I tried this code running Windows 10 and it fails with Err5 Access Denied

I wonder if you can help please.

Thanks
John

As stated in the post, it requires the user to be a member of the Administrators or Account Operators local group on the server. It should work if you use RunAS with login that is a member of such group.

I pass a path and filename that I know exists on the server, but I still get Error 53 - The network path was not found.