Runtime Error 53 "File Not Found" from LotusScript agent (SOLVED) - lotus-notes

For unknown reasons a server-side LotusScript agent is throwing Error 53 "File not found" while trying to read the physical file size of an existing mail archive file. Situation is as follows:
the LS agent is looping all files in a given directory "\archive" right below the server's "\Data" dir. The Server in question is Domino 10.0.1 running on a Windows 2016 Server.
The LS code is looping the directory looking for database files whose names follow a given pattern like "a_EmployeeID.nsf". If a DB's filename fits the pattern the code scans the server's names.nsf for the archive owner using the EmployeeID from the filename. If no person doc for that ID is found the code tries to read the db's physical file size using FileLen(filePath & FileName). Then the resulting data (filepath + file size) + EmployeeID are written to a report file on disk. Files that don't follow the pattern are also at least written back to the report. The idea behind that agent is to find "orphaned" or misplaced databases.
For ~80% of the files scanned this works fine, the records with exact file sizes are written to the report. But for the other ~20% runtime error 53 "File not found" pulls up. In those case the record just contains the file path/name + EmployeeID (if available) + "-1" for the file size. Because the file obviously do exist I assume that this is an access or security problem.
The agent is signed with an admin ID having maximum access rights on both the server and the archive files in question (the ID by policy has Manager access in the dbs' ACLs).
The agent's security settings are set to level 3 (unrestricted access with full admin rights) because I first had signed the agent with an ID having Full Admin Access on the server (same result as with the now used ID).
Comparing the databases' ACLs I can't find any differences between those that "work" and the ones that don't. What I see, though, is that it's apparently always the same databases that are throwing this error, so this isn't a random problem.
For completeness here are the crucial parts of the agent code:
sFileName = Dir$(sPath & "*.nsf")
Do Until sFileName = ""
iCount = iCount + 1
sEmpid = "" 'reset
lSizeArc = 0 'reset
dblSizeArc = 0 'reset
sSizeArcFmt = "" 'reset
If(sFileName Like sPattern) Then
sEmpid = Left(Right(sFileName, Len(sFileName) - 2), 6)
Set vec = vwEgid.Getallentriesbykey(sEmpid, True)
If(vec.count = 0) Then
On Error 53 Resume Next 'Error 53 ("File not found")
lSizeArc = FileLen(sPath & sFilename)
If(Err = 53) Then
lSizeArc = -1
sSizeArcFmt = "-1 (no size available)"
Err = 0
Else
dblSizeArc = Round((lSizeArc / 1024 / 1024), 3)
sSizeArcFmt = Format$(dblSizeArc, "0.000") & " MB"
End If
Print #iFileNum,_
"ORPHANED_ARCHIVE;" & sEmpid & ";" & sFileName & ";" & sSizeArcFmt
End If
Else
On Error 53 Resume Next 'Error 53 ("File not found")
lSizeArc = FileLen(sPath & sFilename)
If(Err = 53) Then
lSizeArc = -1
sSizeArcFmt = "-1 (no size available)"
Err = 0
Else
dblSizeArc = Round((lSizeArc / 1024 / 1024), 3)
sSizeArcFmt = Format$(dblSizeArc, "0.000") & " MB"
End If
Print #iFileNum,_
"BAD_FILE_PATTERN;NO_EGID;" & sFilename & ";" & sSizeArcFmt
End If
sFileName = Dir$() 'next file
Loop
Before I start moving in a different direction like looking into using Windows shell or .dll commands I'd really like to understand why in some cases the code insists that some of the files looked at cannot be "found".
Any ideas?
UPDATE 2021-05-19
So finally I found the solution to that weirdness (and it's not a compliment to my programming skills, I admit):
looking again at the files that are throwing error 53 I realized that they all are rather big, > 2.1 GB to be exact. So I have to admit that I made a stupid programming error: assigning a value that size to a LONG variable won't work, of course. Stupid amateur error ...
(But then why doesn't the code throw the proper error telling the value exceeds the limits, as it usually does?)
Anyways, so I changed the variable to be a DOUBLE.
But: result is still the same, though >> error 53.
Then again looking at Designer help I found this small note:
FileLen returns a Long value
In other words: FileLen itself cannot handle files that size, and that error apparently is thrown before the interpreter finds out about my bad coding.
In other words: no way to solve my problem that way. Back to #TorstenLink's comment: I'll use his method now
Extremely strange error message, still, I would say...
Thanks to everyone helping me think ;)

LotusScript is probably making a call to a 32 bit WinAPI to obtain the file-length and this call will be returning a unsigned 32 bit value, for a maximum file size of ~4.2Gb. However, LS doesn't support unsigned 32 bit values, only signed, so any value returned over 2.1Gb is represented by a negative value.
You can take the absolute value of the negative value and put it in a currency or single/float, and add 2.1Gb to get a file-size up to 4.2Gb, but no bigger, since the API call won't be returning a bigger number and therefore, the API call probably doesn't consider this an error and LS hasn't technically 'overflowed' generating a size error.
Alternatively, if you consider any size over say 1Gb as 'reportable' just look for values that are either +ve and > 1gb or < 0 as any negative value means 'big'. I'm guessing the exact file size isn't that important to you.

Since you only READ from disk: DIR and FileLen, you should'nt suffer from Domino LOCK on the file (but don't try to write !)
I suggest you to change your code in order to determine where it falls and on which files:
f(vec.count = 0) Then
placeInCode = 1 'declare this before as int
On Error 53 Resume goto handelError'Error 53 ("File not found")
in the second part, affect placeInCode = 2
then declare a
handelError:
print "we got error 53 on " & sPath & sFilename & " for the " & placeInCode & " part"
resume next

Related

Character Limit of 50 setting Cell.Value

I've built a form that automates part of our process for building Spreadsheets at work it all works fine except when building a string to set as a selected cells value. this is a requirement for error tracing etc.
ct_cell.Value = GetCommentString()
Private Function GetCommentString() As String
GetCommentString = PlantName & " - " & Replace(Mid(ActiveWorkbook.name, 14) _
, ".xlsm", vbNullString) & ", " & FromSheet & " - " & SAP_Automator.cmnt_input
End Function
What we want to happen is for the Comment section in the sheet we are automating to look like the following: PlantName - WorkbookName, TabName - Comment input from form
at the moment most are coming out around 54 - 55 chars long and vba keeps trimming the start of the string off to bring the length down to 50
we really need to keep all these values in this string so we can easily trace errors when they occur.
is there a way around this? after a bit of googling it seems that vba's char limit is 255 and a cells char limit is 32k+ so this doesn't make any sense to me that is keeps trimming back to 50
any help would be appreciated
Found the issue. the variable PlantName was not set as a public variable so didn't exist within the context of the GetCommentString() function
this happened after I done a code cleanup last week and separated alot of the clutter out into separate functions

Why do we need to use vbObjectError constant when raising user-defined errors in VBA? [duplicate]

This question already has answers here:
What numbers should be used with vbObjectError?
(3 answers)
Closed 10 months ago.
Microsoft Visual Basic for Applications 7.1; Version 1088
I created a custom error handler by following online tutorials and I've been using it for about a year now but I still don't get the part where vbObjectError constant is added to a number between 513-65535, which is reserved for user-defined errors. Most tutorials would recommend to use Err.Raise vbOjectError + 1000 as an example to generate custom errors. The reason for this is to avoid overlapping with errors 0-512, which is reserved for system errors. If I have to write a code around that idea, the code would look like this:
Option Explicit
Sub raiseError()
On Error GoTo errorHandler
Dim x As Double
Dim y As Double
Let x = 4.8
Let y = 5.5
If x <> y Then
Err.Raise vbObjectError + 1000
End If
errorHandler:
Select Case Err.Number
Case vbEmpty
MsgBox "alright!"
Case vbObjectError + 1000
MsgBox ("User-defined error '" & Err.Number & "':" & _
vbNewLine & _
vbNewLine & _
"X is not equal to Y")
Case Is <> vbObjectError + 1000
MsgBox "All other errors"
End Select
End Sub
Now, quoting Microsoft from this documentation:
"Visual Basic errors (both Visual Basic-defined and user-defined errors) are in the range 0–65535. The range 0–512 is reserved for system errors; the range 513–65535 is available for user-defined errors.
When setting the Number property to your own error code in a class module, you add your error code number to the vbObjectError constant. For example, to generate the error number 513, assign vbObjectError + 513 to the Number property."
But what confuses me is vbObjectError constant has a value of -2147221504. As you can see if you run the code, the sum of vbObjectError and 1000 or any number between 513–65535 is far from the range 513–65535, which is available for user-defined errors according to Microsoft.
If I need to use error numbers in the range of 513–65535, why not use those numbers directly like Err.Raise 513 or Err.Raise 1000?
I'd really appreciate any clarifications from you guys. Thank you all very much.
I finally found a good answer to this question. The Microsoft documentation for Err.Number explains it well and provides this example:
' Using Number property with an error from an
' Automation object
Dim MyError, Msg
' First, strip off the constant added by the object to indicate one
' of its own errors.
MyError = Err.Number - vbObjectError
' If you subtract the vbObjectError constant, and the number is still
' in the range 0-65,535, it is an object-defined error code.
If MyError > 0 And MyError < 65535 Then
Msg = "The object you accessed assigned this number to the error: " _
& MyError & ". The originator of the error was: " _
& Err.Source & ". Press F1 to see originator's Help topic."
' Otherwise it is a Visual Basic error number.
Else
Msg = "This error (# " & Err.Number & ") is a Visual Basic error" & _
" number. Press Help button or F1 for the Visual Basic Help" _
& " topic for this error."
End If
MsgBox Msg, , "Object Error", Err.HelpFile, Err.HelpContext
The key is that you are expected to subtract the large negative number from Err.Number and do your Select Case on the result. 0 - 65535 means the error was generated by Err.Raise.
As nobody answered, I'll hazard a (hopefully educated) guess. The "error numbers" generated aren't really those between 0 and 65535 (2^16-1). Those numbers are error offsets that create a negative error code.
For example, in Access, you'll often see positive error numbers (like 3146 if you can't connect to a DAO workspace). I wouldn't call those "user errors", but they are out of the "system error" range.
Also, if the true error numbers couldn't be larger than 65535 (the largest signed 16-bit number), you wouldn't need a Long type to hold them.
Finally, if you've ever noticed some error codes displayed by Microsoft products, you'll see hex codes like "8xxxxxxx", which are large negative numbers in signed decimal, as if they added vbObjectError to their own internal error numbers.
So your error messages between 513 and 65535 are really convenient numbers for you to use to create error codes by adding to vbObjectError. I suppose it also makes it easier to create a type that stores error information (error number, Description, Help ID, etc.) and create an array of those types. For example, Errors(0) would return the type with error number 513 and other information that you might need in your ERR.Raise statement.
But why is vbObjectError equal to -2147221504? I don't know. That's not -(2^31) + 512. In hex, it's FFFFFFFF80040000. I guess you'll have to ask Microsoft about that one.
For more information, here's another site's thoughts on this: http://www.vbforums.com/showthread.php?570075-vbObjectError-what-is-it-for
It's better and more secure use vbObjectError + 1001 based on this Microsoft article, Trappable errors, where it is said:
Unused error numbers in the range 1–1000 are reserved for future use by Visual Basic.
I was using vbObjectError + 513 a lot in my Enums definitions inside Interfaces for controling errors in the Classes that implement that interfaces but on one ocassion I've got an ambiguous name error while I was trying to use a second element from an Enum where the first element were equal to vbObjectError + 513.
Public Enum WorkbookXErrors
InvalidExtension = vbObjectError + 513
CannotCreateObject '***I got an error trying to use this Enum element.***
End Enum
Solution: I only changed to InvalidExtension = vbObjectError + 1001 and since then everything it's ok.
Greetings,

Using strings for network path, not mapped drive, with Shell in VBA

QUESTION UPDATED, PLEASE SEE BELOW
I am trying to use string variables (exedir - the full network drive directory of an exe file, and inputdir - the location of the input file argument) to launch an exe with its argument using shell in VBA.
The commented out line in the script below does not work. If I manually set the drive using a letter I can get it to work, as shown below, but the .exe and .lam input file used here are both on networked drives (the variable comp is the unique name of the users PC, which sets the name of the .lam input file, so the input file name is different for every user).
I'm not too familiar with the syntax and borrowed it from elsewhere. Am I missing a quotation mark or maybe have too many in the commented out row?
Sub CallExeWithInput()
Set wsh = VBA.CreateObject("WScript.Shell")
'Statusnum = wsh.Run(Command:="""" & exedir & """""""" & inputdir & """""", windowstyle:=1, waitonreturn:=False)
SetCurrentDirectory "M:\path\"
Statusnum = wsh.Run(Command:="foo.exe ""M:\path\" & comp & ".lam""", windowstyle:=1, waitonreturn:=False)
End Sub
Example of exedir: \\network\path\foo.exe
Example of inputdir: \\network\path\compname.lam
Example of comp: compname << found using Environ$("computername")
I'm aware of some previous questions, having read many to get this far.
I don't want to provide the letter of the drive, and ideally want to launch the .exe with the input file using string variables to input everything. One major reason I want to only use strings, is that they can be controlled by a single variable, and when the directory changes (say with an upgrade of the .exe) it will be easy to update this script.
UPDATE
Following the comments kindly provided below I arrived here:
SetCurrentDirectory fldr
Statusnum = wsh.Run(Command:="foo.exe " & quote & inputdir & quote, windowstyle:=1, waitonreturn:=False)
Where quote = chr(34) and fldr = \\network\path\
Interestingly, if inputdir is defined as a path to a lettered drive (inputdir = M:\etc), this works. If it is to a network drive path (inputdir = \\network\etc), it launches but the .exe immediately crashes with an invalid floating point operation.
How can I alter this so the input file can be provided as a network path?
If this is not possible I will presumably need a subroutine that locates which letter the user has mapped the network drive onto in order to build my string, such as this:
path = ":\foo\bar\"
dim x as integer
for x = 65 to 90
If CBool(Len(Dir(Chr(x) & path))) Then
msgbox "It's in drive " & Chr(x)
exit for
end if
next x

After a WMI search in VBScript, can I create my search filter BEFORE my "For Each" statement?

I've created an alternative search utility to the Windows search utility with VBScript using a WQL search, but, as it turns out, it's quite slow. I would like to speed it up and I think I can do it, but I would need to place my search filter AFTER my WQL search and BEFORE my For Each statement. Is this even possible?
I've already tested by filtering in the WQL search, but it's about 40% faster if I filter after the WQL search. I've also tested with and without iFlags, but they tend to slow the search quite a bit, even though MS seems to believe otherwise.
Since the user can search by filename, creation date, last modified date and/or file size, if the filter is after the For Each statement then the script has to create the search filter each time it enumerates a file. I'd like to create the filter once in the hope of shaving some time off the search.
This will probably make better sense when you take a look at the snippet of code I've posted. Note that the sub subCreateSearchString will have calls to other search options and functions (ie: convert from UTC to local time, format file sizes, etc.)
Dim strSearchName, strComputer, objSWbemServices, objFile, colFiles
Dim strFileName, strReturnedFileName, strQueryDriveAndPath
strSearchName = "test" 'Text being searched for - change as needed
strQueryDriveAndPath = "PATH = '\\Drop_RW\\' AND DRIVE = 'D:'" 'Path and drive in which to search - change as needed
strComputer = "."
Set objSWbemServices = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colFiles = objSWbemServices.ExecQuery("Select * from CIM_DataFile WHERE " & "" & strQueryDriveAndPath & "")
'* I'd like to place the call to "subCreateSearchString" here
On Error Resume Next
For Each objFile in colFiles
strReturnedFileName = objFile.Name
subCreateSearchString ' Search filter - it works when placed here
If strSearchForString Then
MsgBox "File matches:" & vbCrLf & strReturnedFileName
Else
MsgBox "File DOES NOT match" & vbCrLf & strReturnedFileName
End If
Next
Sub subCreateSearchString
'* Set Filename Variable for search:
strFileName = InStr(LCase(strReturnedFileName), LCase(strSearchName))
strSearchForString = strFileName
End Sub
Since you depend on the names of the files you're iterating over in the For Each loop: no, not possible.
I'd strongly recommend making some adjustments, though.
Use a Function rather than a Sub if you want to return something from a subroutine.
Avoid using global variables. They have a nasty tendency of introducing undesired side effects and also make debugging your code a pain in the rear. Pass values into your subroutines via parameters, and return values as actual return values.
The returned value is an integer (or Null), but you use it like a boolean and named your variables (and sub) as if it were a string. Don't do that. Name your functions/procedures after what they're doing, and name your variables after what they contain. And if you want to use a boolean value make your function actually return a boolean value.
Avoid Hungarian Notation. It's pointless code-bloat the way most people use it. Even more if your naming doesn't even match the actual type.
Do not use global On Error Resume Next. Ever. It simply makes your code fail silently without telling you anything about what actually went wrong. Keep error handling as local as possible. Enable it only for single commands or short code blocks, and only if there is no other way to avoid/handle the error.
Function IsInFilename(searchName, fileName)
IsInFilename = InStr(LCase(fileName), LCase(searchName)) > 0
End Function
For Each objFile in colFiles
If IsInFilename(strSearchName, objFile.Name) Then
MsgBox "..."
Else
MsgBox "..."
End If
Next

Division by zero with positive or no divisor (vba)

I'm new to macros and vba in Excel. Currently, I'm working on a vba macro for an invoice template at work.
However, I'm running in a division by zero error that I'm having trouble tracing the cause of.
There are two specific lines of code where it pops up, sometimes..
First part:
VATRMB = 0
Second part:
VATRMB = VATRMB + (0.0593 * (ActiveSheet.Range("I" & i).Value / (1 + 0.0593)))
The Dim VATRMB is stored as follows:
Dim startRow As Integer, endRow As Integer, VATRMB As Single, VATEUR As Single, VATUSD As Single, VATRMBCell As Range, VATEURCell As Range, VATUSDCell As Range
The way I see it these lines should never throw up a division by zero error. In the first case there is no divisor whatsoever and in the second it is always positive.
Have any of you got an idea as to why this might cause an error? Could it have anything to do with the fact that the sub gets called multiple times, reusing the same VATRMB Dim? It should be reset after each call of the sub, right? Or could it have to do with the fact that I specify VATRMB as Single? This is appropriate for 'small' (sub-1,000,000) floating numbers, correct?
EDIT:
1. Added exact line used for calling Dim storage
2. Here is the full block of code used, maybe it helps to clarify a thing or two:
'Debug.Print Tab(10); ("Items will be searched in rows " & startRow & " thru " & endRow) 'Serves for debugging and testing
For i = startRow To endRow 'Loop the following code through all rows mentioned above
If ActiveSheet.Range("B" & i).Find("Membership") Is Nothing Then 'If nothing is returned when searching for "Membership"; i.e. if the item in this row is not a membership payment
If Not ActiveSheet.Range("H" & i).Find("RMB") Is Nothing Then 'If the value for this item is RMB denoted
'Debug.Print Tab(20); "Item on Row " & i & " is RMB denoted, VAT = " & ((ActiveSheet.Range("I" & i).Value / (1 + 0.0593)) * 0.0593) 'Serves for debugging and testing
VATRMB = VATRMB + (0.0593 * (ActiveSheet.Range("I" & i).Value / (1 + 0.0593))) 'Add row's VAT to VAT total
End If
If Not ActiveSheet.Range("H" & i).Find("EUR") Is Nothing Then 'If the value for this item is EUR denoted
'Debug.Print Tab(20); "Item on Row " & i & " is EUR denoted, VAT = " & ((ActiveSheet.Range("I" & i).Value / (1 + 0.0593)) * 0.0593) 'Serves for debugging and testing
'MsgBox VATEUR + 0.0593 * ActiveSheet.Range("I" & i).Value / (1 + 0.0593)
VATEUR = VATEUR + (0.0593 * (ActiveSheet.Range("I" & i).Value / (1 + 0.0593))) 'Add row's VAT to VAT total
End If
If Not ActiveSheet.Range("H" & i).Find("USD") Is Nothing Then 'If the value for this item is USD denoted
'Debug.Print Tab(20); "Item on Row " & i & " is USD denoted, VAT = " & ((ActiveSheet.Range("I" & i).Value / (1 + 0.0593)) * 0.0593) 'Serves for debugging and testing
VATUSD = VATUSD + (0.0593 * (ActiveSheet.Range("I" & i).Value / (1 + 0.0593))) 'Add row's VAT to VAT total
End If
Else 'Else, i.e. if the row contains a membership payment, then essentially nothing happens
'Debug.Print Tab(20); ("Item on Row " & i & " is a membership payment; no VAT paid.") 'Serves for debugging and testing
End If
Next
So what I'm trying to do is basically loop through all the items in the invoice, from startRow to endRow, and determine whether the item is a membership payment by parsing the 'type' string (column B). Then, depending on whether or not it is a membership payment determine the VAT, also checking the currency in which it is paid. The amount for the payment is stored in Column I as a floating number.
Not sure if this is the answer to your problems since you would need to provide the entirety of your workbooks etc to confirm. Nevertheless, we can create this type of "it should be impossible" situation with 100% reproducibility for not only Div0, but also for pretty much any error, with a line like:
VarX = 10 ' we can make this fail with Div0, Overflow or whatever
In our test, the problem is not actually the "direct" or "explicit" code where the error is reported, but rather, the error occurs elsewhere, and VBA in its infinite wisdom just happens to "report" the error in an odd way at an odd time (in fact it should not be reporting certain errors at all, see below).
Does your package involve any external executables, dll's, addins', etc?
If so, then that is likely the place to start.
If not, the error may actually be occurring directly or indirectly in the Range you are accessing, but not necessarily in the cell currently accessed.
Here is an example creating a "Div0" via a DLL accessed in VBA as an addin: Suppose you write a bit of code in another language, here Fortran (we use Implicit None everywhere, and everything is declared correctly etc.):
Pure Subroutine FortDLL(x, U)
:
Real(DP), Intent(In) :: x
Real(DP), Intent(Out) :: U
:
Real(DP) :: QQ
:
:
QQ = Log10(x) ! Notice this is not the return var and has nothing to do with anything on the VBA side (or so you would think)
:
U = 10.D0 ! Notice, this is the return var, and is a constant = 10.D0
:
End Subroutine FortDLL
compile as DLL and access in the usual way.
Then suppose you have some VBA as:
Function VBAFunc(x) as Variant
:
Call FortDLL(x, U)
:
Dim VarU as Variant
:
VarU = U ; you can replace this with VarU = 10, or whatever, and will get same result/failure
Now, if x < 0, then the DLL will crap out since Log10 is not defined for x < 0. This will throw a Fortran run time error, and depending on how you set this up, you can get it to throw a Div0, an Overflow (e.g. on the Fortran side the MaxExponent for a Double here is 1024, whereas on the VBA side it is around 308 depending on a number of things, etc. etc. etc. )
Now even though QQ has nothing at all to do with the VBA side, when the VBA code executes FortDLL(), it returns U = 10, and it actually does that part correctly.
HOWEVER, the DLL would have thrown a Div0 (or whatever you desire to create) error, and that "error message" is/can be buried in the return to the Call FortDLL().
If you are not using DLL's etc, it is possible that something comparable is happening in your "range" or elsewhere during you looping etc.
We have not performed explicit tests as to why the Dim as Currency "fix" works, but we are guessing that as Currency is a very special Type (it is actually a structured type with at least TWO fields), the "error message" may be buried in one of those "fields", and which may not be required/relevant to the size of number you are using, and obviating the crash by "fluke" (i.e. a kind of "lucky KLUDGE". You can test this by putting in numbers too large for Double, and requiring the full machinery of the Currency Type. If it is a "lucky KLUDGE", then one day when you are not around and somebody else is using your code, and they enter a number requiring the full Currency machinery, then it will likely crash, and nobody will know why.
Here is an alternate test, suppose you have the crash on a line like VarX = 10, then replace/amend as follows:
:
On Error Resume Next
VarX = 10
VarX = 10
:
... if this "works" (i.e. obviates the error/crash), then your problem is likely along the lines explained above (whether "external" or "internal"). In this case, basically, the "Div0 problem" is treated as a VBA error on the first time VarX is assigned 10, since the Error Trap is set, that "first time" catches and ignores the "DLL side error", and moves on.
... clearly this is just a TEST, not a solution.
This may also be Excel/Win/Compiler (and especially with GCC, compiler VERSION also since they have some pretty wacky things/changes sometimes) dependent and so the reproducibility and exact behaviour may vary.

Resources