Hi Larry,
>Question: Where should such code best be put in MM (I'm new to MM)?
>I tried putting it in the BeforeReadEvents code, but then the OnShutDown() code fails.
as Chris already mentioned, the application object is a good place. I'd like to recommend that you put that code in the app class PreInitHook() method, but there seems to be an issue that the cleanup code isn't really run in case you're returning .F. from there. I don't know if this could cause any trouble, but IMO, the safer way is to use the Init() at the moment.
IF NOT FileIsValid()
This.CleanUp()
RETURN .F.
ELSE
RETURN DODEFAULT()
ENDIF
>I tried putting it in Setup.Prg but I got other errors.
cApplication is the better place! If you're modifying setup.prg, you'll always have to readmit your changes after a framework update. Putting the code in your application class at the application layer would avoid this.
>(a) The file is always in the same folder as the app's exe.
That's good, since the current directory is set to the main project directory or (in case of an exe) to the directory where the exe resides, so you can simply use SYS(2000) to check for the existance of the file. The directory is set in setup.prg, so everything is fine for you, if you're putting your check into the app class.
>(b) Users are not allowed to login unless the external file validates True (this is startup security, not login security).
>(c) FileIsValid() makes use of routines in a procedure file whose 'Set Proc To' happens in goApp.BeforeReadEvents.
There shouldn't be a need to put an explicit SET PROC TO anywhere. All prg and vcx files are gathered at startup of your application in debug mode and when running setx.prg from your project directory. So you should be able to take this command out of there. All the SET PROCs and SET CLASSLIBS are being done in setup.prg, so they're available from the first second on when the Application object is instantiated.
Regards,
Armin