Link to home
Start Free TrialLog in
Avatar of CareyJ
CareyJFlag for United States of America

asked on

How To: Tell which users are running an EXE file

Have you ever tried to replace an .EXE file but couldn't because
another user was currently running the program?

The network is NT4, but the EXE files are located on a Netware Server.

From a VB function, how can I tell who is using another program?  
ASKER CERTIFIED SOLUTION
Avatar of inthedark
inthedark
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
PS The startup exe should also check that the message.txt file does not exists and only startup the current exe if there is no message to display.  If it finds a message file you simply display the message

MsgBox message,vbExclamation "System Close"
End

This solution also works well if you are remotly supporting you clients, as I do either using radmin via the internet or by PcAnywhere.  You can just blat your new version onto the system with no more pain.

Been there! Done that! Ain't goin back!
Another tip is to create a timer which autocloses the application say at 10:00pm after all users have gone home, before a backup starts. In case sombody left there pc switch on but still running you app.

Also another tip:

When your app starts up it trys to open a signal file for exclusive non shared access.  If it gains access you are the only user of the system you then clear a files from a current users folder.  And reopen the sginal file for shared access.  You then save the current logged in user login name to a file in the current users folder example  "s:\Users\Nick.txt", "s:\Users\Jim.txt" when the main form unloads you delete (Kill) the user's file ans also close the signal file.

In this way you can see who is on the system ans also operations like a month end update can know if they are the only user, and create a message file to stop any new users running the application.
Avatar of Jonyv
Jonyv

The "NetFileEnum" API function can be used to get this information, BUT this requires that you are a member of the Administrators or Account Operators local group. If you are just a regular user you are out of luck.

One way to solve this problem (if you get along well with your network administrator...) is to implement a Windows NT service that runs on the administrators account and then have the service communicate with a VB client via a pipe or something similar. I experimented with such a solution a while ago, but the need for it disappeared before it was finished...  :-)
What I do is create a little program that the users run which copies the exe from the server into a local directory on the clients PC.  That way you can happily replace the exe on the server and just tell the users to restart the app.

I have this program running ALL our in house apps, I just pass the name of the exe as a command line parameter and then it copies it to the local drive and runs it.

I know this doesn't specifically answer what you asked but i hope it helps.
You have a couple of problems to deal with here.

1. You need to get existing users out of the application, hopefully gracefully.

2. You need to prevent new users from launching the application.

RESOLUTION:
   You need two files to make this arrangement work. The first file is your executable. The second is a file I name permissions.cfg.  

========================================================
' IMPLEMENTATION:
   At runtime both files need to be on the server it is not required they be in the same directory. To allow program execution leave permissions.cfg wherever it is. To halt program execution and to prevent further access delete permissions.cfg.
========================================================

   In your executable you have either Sub Main() or a Form_Load() that signals the beginning of your application.  In whichever routine you use call a function named AllowExecute. In your call give the location of the permissions.cfg file as the argument. (Function Below)

I.E. AllowExecute("\\ntserver\public\exe\MyApp\Permissions\permissions.cfg")

If AllowExecute is False display a message box to the user that says:

 MsgBox("This application is being modified by the Administrator and is currently not available.")

Then close gracefully.

This will deal with problem 2.

To deal with problem 1 you need to timers on a form. I will call them tmrPermissions and tmrCloseGracefully.
tmrPermissions needs to have an interval of 30000 or 30 seconds. tmrPermissions calls AllowExecute. If AllowExecute returns true then tmrPermissions does nothing and keeps running. If AllowExecute returns false then tmrPermissions has a message box that states:

MsgBox("This program will be closed in 10 minutes for server administration please save your work and exit.")

tmrPermissions then set tmrCloseGracefully to 10 minutes and enables it then shuts itself off. tmrCloseGracefully does nothing for 10 minutes. If at the end of 10 minutes it is still up it calls a routine that saves all work and closes down.

Now you may replace the executable.

Here is the code I would use:

'------------------------------------------------------>

Private Sub Form_Load()

    If AllowExecute = False Then
        MsgBox("("This application is being modified by the Administrator and is currently not available.")
        CloseGracefully
    End If

    tmrPermissions.Interval = 30000
    tmrPermissions.Enabled = True

End Sub

'------------------------------------------------------>

Private Function AllowExecute(ByVal CFG_PATH As String) As Boolean

   If Dir(CFG_PATH) <> "" Then
       AllowExecute = True
   Else
       AllowExecute = False
   End if

End Function

'------------------------------------------------------>

Public Sub CloseGracefully()

'Comments: This routine should save all work and application settings prior to ending session.

    '*-- You must define what needs to be done here.

End Sub

'------------------------------------------------------>

Private Sub tmrPermissions()
   
     If AllowExecute = False Then
         tmrPermissions.Enabled = False
         MsgBox("This program will be closed in 10 minutes for server administration please save your work and exit.")
         tmrCloseGracefully.Interval = 100000
         tmrCloseGracefully.Enabled = True
     End If

End Sub

'------------------------------------------------------>

Private Sub tmrCloseGracefully()

    'SafetyCheck
    If AllowExecute = False Then
        tmrCloseGracefully.Enabled = False
        CloseGracefully
    Else
        tmrCloseGracefully.Enabled = False
        tmrPermissions.Interval = 30000
        tmrPermissions.Enabled = True
    End If

Exit Sub
Hi rawinnlnx9, don't forget that you can't use message box as it will disable the timer.  So it you try to close the app after the user went to the dentist it will just keep running until somebody presses enter.
Avatar of CareyJ

ASKER

I believe all of you have encountered this "net app developer" problem because you are describing the problem better than I did in the Question.

All of my apps have user "User Logging".  The user log for this app is appended as each user runs the program.
Looking at the user log only shows me who has ran it today...not who is still in the program.  The app is started about 50 times an hour....some short visits, some long visits.

I also use a timer to check for the existence of a lock file.  I place a lock file in the App directory whenever I need users to exit the program.  Normally, after a minute passes, all users have exitted the program.  The lock file also stops other users at startup by showing the "Busy...try again later" form for 5 seconds then unloading the app.  This is innefective when a user is in the program, sitting at a dialog box or on some other form in the program.  

Jonyv suggested an API call.  That's the sort of answer I'm looking for.  I want to see who is in the program "right now".  Do any of you have examples of usage of "NetFileEnum"?
Avatar of CareyJ

ASKER

Oh, btw, InTheDark...I don't have access to the Netware server to run "System Monitor".  The server is at another site.  I have to send a request to network support at that site and wait for their answer.  By then, the current users list will have changed completely.

I am the Netware "file owner" and "folder owner".
I made a quick "VB version" of NetFileEnum, BUT I'm not in a network environment at the moment so I haven't been able to test if it actually works  :-)

Anyway, if it works as it's supposed to the call to NetFileEnum will return an array of FILE_INFO_3 structures, the "pathname" and "username" members can be used to see what files are open by what users. The enumeration can be limited with the "BasePath" and "UserName" parameters to NetFileEnum.

And as mentioned before: This code must be run with administrators privileges to even have a remote chance of working...

Put the following code in a BAS module and just call NetFileEnum with the servername (like "\\servername"). Specify "VbNullString" for "BasePath" and "UserName" if you're not going to use them.

*begin code*

Private Declare Function NetApiBufferFree Lib "Netapi32.dll" (ByVal lpBuffer As Long) As Long

Private Declare Function Internal_NetFileEnum Lib "Netapi32.dll" Alias "NetFileEnum" (ByVal ServerName As String, _
                                                                                      ByVal BasePath As String, _
                                                                                      ByVal UserName As String, _
                                                                                      ByVal level As Long, _
                                                                                      ByRef BufPtr As Long, _
                                                                                      ByVal prefmaxlen As Long, _
                                                                                      ByRef entriesread As Long, _
                                                                                      ByRef TotalEntries As Long, _
                                                                                      ByRef resume_handle As Long _
                                                                                      ) As Long

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, _
                                                                    pSource As Any, _
                                                                    ByVal dwLength As Long)


' Converts a Unicode string to an ANSI string
Private Declare Function WideCharToMultiByte Lib "kernel32" _
                            (ByVal Codepage As Long, _
                            ByVal dwFlags As Long, _
                            lpWideCharStr As Any, _
                            ByVal cchWideChar As Long, _
                            lpMultiByteStr As Any, _
                            ByVal cchMultiByte As Long, _
                            ByVal lpDefaultChar As String, _
                            ByVal lpUsedDefaultChar As Long) As Long


Private Const ERROR_ACCESS_DENIED = 5&
Private Const ERROR_INVALID_LEVEL = 124&
Private Const ERROR_MORE_DATA = 234
Private Const ERROR_NOT_ENOUGH_MEMORY = 8
Private Const NERR_Success As Long = 0&
Private Const MAX_PREFERRED_LENGTH = &HFFFFFFFF
Private Const NERR_BufTooSmall = &H84B
Private Const CP_ACP = 0        ' ANSI code page

Private Type Internal_FILE_INFO_3   ' As declared in Lmshare.h
    fi3_id As Long
    fi3_permissions As Long
    fi3_num_locks As Long
    fi3_pathname As Long
    fi3_username As Long
End Type

Public Type FILE_INFO_3     ' VB version of the above structure
    fi3_id As Long
    fi3_permissions As Long
    fi3_num_locks As Long
    fi3_pathname As String
    fi3_username As String
End Type


' NetFileEnum "VB Style"
Public Function NetFileEnum(ServerName As String, BasePath As String, UserName As String) As FILE_INFO_3()

Dim Buffer() As Internal_FILE_INFO_3
Dim BufPtr As Long
Dim RetBuffer() As FILE_INFO_3
Dim NrOfEntries As Long
Dim TotalEntries As Long
Dim Status As Long
Dim i As Integer

' request level 3 information
Status = Internal_NetFileEnum(ServerName, BasePath, UserName, 3, BufPtr, MAX_PREFERRED_LENGTH, NrOfEntries, TotalEntries, ByVal CLng(0))

If Status <> NERR_Success Or NrOfEntries <= 0 Then Exit Function

ReDim Buffer(0 To NrOfEntries - 1)  ' Allocate temporary buffer
ReDim RetBuffer(0 To NrOfEntries - 1)   ' Allocate return value array

CopyMemory Buffer(0), ByVal BufPtr, NrOfEntries * Len(Buffer(0))  ' Copy enumerated data into temporary buffer

For i = 0 To UBound(Buffer)     ' Copy and convert temporary buffer into array of FILE_INFO_3
    With RetBuffer(i)
        .fi3_id = Buffer(i).fi3_id
        .fi3_num_locks = Buffer(i).fi3_num_locks
        .fi3_permissions = Buffer(i).fi3_permissions
        .fi3_pathname = GetStrFromPtrW(Buffer(i).fi3_pathname)
        .fi3_username = GetStrFromPtrW(Buffer(i).fi3_username)
    End With
Next i

NetApiBufferFree BufPtr     'Free buffer returned from NetFileEnum

NetFileEnum = RetBuffer

End Function

' Returns an ANSI string from a pointer to a Unicode string.

Private Function GetStrFromPtrW(lpszW As Long) As String
  Dim sRtn As String

  sRtn = String$(lstrlenW(ByVal lpszW) * 2, 0)   ' 2 bytes/char

' WideCharToMultiByte also returns Unicode string length
'  sRtn = String$(WideCharToMultiByte(CP_ACP, 0, ByVal lpszW, -1, 0, 0, 0, 0), 0)

  Call WideCharToMultiByte(CP_ACP, 0, ByVal lpszW, -1, ByVal sRtn, Len(sRtn), 0, 0)
  GetStrFromPtrW = GetStrFromBufferA(sRtn)

End Function

' Returns the string before first null char encountered (if any) from an ANSII string.

Private Function GetStrFromBufferA(sz As String) As String
  If InStr(sz, vbNullChar) Then
    GetStrFromBufferA = Left$(sz, InStr(sz, vbNullChar) - 1)
  Else
    ' If sz had no null char, the Left$ function
    ' above would return a zero length string ("").
    GetStrFromBufferA = sz
  End If
End Function


Oops, just noticed that I missed a declaration in the code above...

Add this at the beginnig of the module:
Private Declare Function lstrlenW Lib "kernel32" (lpString As Any) As Long
Avatar of CareyJ

ASKER

Not in a development environment?  Me neither.  I'm on vacation until 3/31.  I can try it then.  Can you try it before then?
You can load rconsole on the server to be able to see it remotely.

Just a thought, If your company is into Netware then you should download the VB netware controls from their site.  These are free.  There are hoards of things you can do with them.  You just add the control you need to your form and away you go.

But the simple way is to load a starter exe, if compiled to pcode its only about 10k and so the user does not see any time delay.
I managed to test it in a very small scale on my local computer. You need to make a small change to the code above, in NetFileEnum, change:

' request level 3 information
Status = Internal_NetFileEnum(ServerName, BasePath, UserName, 3, BufPtr, MAX_PREFERRED_LENGTH, NrOfEntries, TotalEntries, ByVal CLng(0))

If Status <> NERR_Success Or NrOfEntries <= 0 Then Exit Function

To:

BufPtr = 0
' request level 3 information
Status = Internal_NetFileEnum(StrConv(ServerName, vbUnicode), StrConv(BasePath, vbUnicode), StrConv(UserName, vbUnicode), 3, BufPtr, MAX_PREFERRED_LENGTH, NrOfEntries, TotalEntries, ByVal CLng(0))

If Status <> NERR_Success Or NrOfEntries <= 0 Then
    If BufPtr <> 0 Then NetApiBufferFree BufPtr
    Exit Function
End If

Now it should work fine  :-)
InTheDark,

    Yes you are right. I forgot that problem and I forgot to mention I use a form based message box. I also forgot to include the CFG_PATH in my call to AllowExecute.

'= New Comment

I have noticed that many people are going for highly technical API based solutions. I have tried all of them and unless you have Admin rights on the server they are very difficult if not impossible to pull off. My advice we be to implement a very simple but effective solution and then get on with your actual system programming. Problems like the one you describe can consume enormous resources when the fix (any fix, simpler the better) is actually a trivial solution. Try not to go for the coolest fanciest fix out there. Just get something that works and move on. You can always revisit the problem later. I am sure your boss would agree with me on this too.

Just pick an easy solution that has a simple ZERO dependency implementation. Do not rely on API's not rely on Admin rights do not rely on anything but what you can do in the IDE with no declares, references or hooks. I would just get something up and working. You can slay dragons later. :)

Just my two cents. But I have burned days on stuff like this only to learn that I could not see the forest there were too many trees.

Good Luck and See Ya,

Rex
Avatar of CareyJ

ASKER

JonYV:
I think I'm having trouble with the functions paramters.
Please show me how I would call the function to find all users currently running a program as described below:

Server Name  : "MyServer"
App Path/Name: "P:\NetApps\MyProgram\MyProgram.exe"
User Name    : "CareyJ"


Rex:  
I agree with everything that you say.  My apps have simple
fixes...user logging, triggering to cause user exit, and some have inactivity time-out exit routines.

It's just that "sometimes" when I try to update an app, I run into this problem.  Usually the app is sitting at a dialog so the fixes can't work and I can't easily determine who is holding the program open.

I'm not dedicating a lot of time to developing this (that's why I asked this question here - so if someone else had already done it, I wouldn't have to do it also)
Unfortunately, I can't even afford to put a lot of time into testing JonYV's suggestion.  I really need a solution that works straight off of the screen.  It sounds like you work like I do.  I don't have time to slay dragons either. :)

Thanks for adding your comment because other visitors here who view this PAQ later need to learn early to approach these situations without dependency where possible.  



I would do something like this:

Dim OpenResources() As FILE_INFO_3

OpenResources = NetFileEnum("\\MyServer", vbNullString, vbNullString)

On Error GoTo Skip
For i = 0 To UBound(OpenResources)
    If InStr(1, OpenResources(i).fi3_pathname, "MyProgram.exe") <> 0 Then Debug.Print "MyProgram.exe is openend by " & OpenResources(i).fi3_username
Next i
Skip:


Quick explanation of the parameters again:
ServerName - Like it says...

BasePath - BasePath to limit the search. BUT this may not be very useful in this case since this has to be the local path as seen on the SERVER. So if you have the server mapped as P: but the application is on drive C: on the server it's the C:\whatever\path you have to specify here. This is why I just pass vbNullString to get everything and filter the results afterwards.

UserName - If you specify this, the function will only return resources opened by this user, This is probably not what you want, so just use vbNullString here too.

The On Error Goto thing is just because VB doesn't have an easy way to tell if the returned array is empty...

If it still doesn't work, step through the code and check the return value from the internal NetFileEnum API call, it should be zero if it worked.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I assume that you are refering to the parameters being passed as regular VB strings in the call to NetFileEnum in my original post?
In that case you will find that I already posted a correction for this, so I don't think Unicode is a problem here (unless you have spotted some other problem that I missed?)

I have successfully executed the code above (with the fix) and got valid results back, so I think it works  :-)
Avatar of CareyJ

ASKER

I get valid results back when I run it against the SQL server but I get nothing back when I run it against the Netware server.

In NetFileEnum, Status = 53 instead of 0 is returned when I run against the Netware server.
Avatar of CareyJ

ASKER

I get valid results back when I run it against the SQL server but I get nothing back when I run it against the Netware server.

In NetFileEnum, Status = 53 instead of 0 is returned when I run against the Netware server.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi CareyJ.  You are going the up hill way. By using 2 EXE files the job would be finished in 2 minutes and also it would mean that you just dont need to care who is on the network, you could be playing golf instead of waiting for users to quit the application.
Hi CareyJ,
This old question (QID 20563253) needs to be finalized -- accept an answer, split points, or get a refund.  Please see http://www.cityofangels.com/Experts/Closing.htm for information and options.
Avatar of CareyJ

ASKER

Well...I haven't forgotten about this question

The need for an answer still exists.

Jonyv was closest to the answer when he offered an NT Server solution but I still need the Netware solution.

I don't want to delete the question because it has some good information pertaining to this problem.

I don't want to close the question because I hope someone sees it soon and offers the Netware solution.

What do you recommend CleanupPing?  Do you really need it closed?
CareyJ:
This old question needs to be finalized -- accept an answer, split points, or get a refund.  For information on your options, please click here-> http:/help/closing.jsp#1 
Experts: Post your closing recommendations!  Who deserves points here?
Avatar of CareyJ

ASKER

Well...I haven't forgotten about this question

The need for an answer still exists.

Jonyv was closest to the answer when he offered an NT Server solution but I STILL need the Netware solution.

I don't want to delete the question because it has some good information pertaining to this problem.

I don't want to close the question because I hope someone sees it soon and offers the Netware solution.

What do you recommend CleanupPing?  Do you really need it closed?
No comment has been added lately, so it's time to clean up this TA.
I will leave a recommendation in the Cleanup topic area that this question is:

 -->PAQ - with points refunded

Please leave any comments here within the next seven days.

PLEASE DO NOT ACCEPT THIS COMMENT AS AN ANSWER

GPrentice00
Cleanup Volunteer
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
If you go look at my original solution I posted and change the message box call to a custom form you design to look like a message box it will work just fine. Then it will not matter who is logged in. They have 10 minutes and then they get iced. You can only do so much to help people. Or make it 30 minutes either way... Put in a custom form and things will work no matter what server you are trying to hit. You are not going to find a "silver bullet" on this one.

- Rex