I launch Python through a VBA macro. I want a proper error message if the Python path isn't good.
I use this macro on 2 different computers, and the Python paths are not the same on those 2 computers. The 2 paths are on 2 differents Cells name path_Python and path_Python_2
Here is my code :
Function launchPython() As Boolean
Dim pathScript As String
pathScript = [path_files] + "code.py"
On Error GoTo err
Shell [path_Python].Value & " " & pathScript
launchPython = True
Exit Function
err:
On Error GoTo err2
Shell [path_Python_2].Value & " " & pathScript
launchPython = True
err2:
launchPython = False
MsgBox "Error: please check Python paths"
End Function
Problem is, when 2 the paths are not good, instead of going to err2, I have a VBA error message blocking on
err:
On Error GoTo err2
Shell [path_Python_2].Value & " " & pathScript
How can I solve this ?
Thank you very much
Instead of trying to shell a command to a potentially missing path, I would check that a file exists and try that. You can use one of many functions such as FileExists in http://allenbrowne.com/func-11.html, or just check that dir$ returns non blank.
Dim Path as string
path = path_python
if dir(path) = "" then
path = path_python2
if dir(path) = "" then err.raise
end if
shell path & " " & pathScript
Related
..................................................................................................................................................................
Late-breaking news...
P.P.S. I just read that FileSystem.FileCopy is better than just FileCopy. That's what I'm going to try. But I really would like to know how to use FileCopy inside a loop, meaning, "How do I close files used in FileCopy?" For the big picture made clear, read on.
..................................................................................................................................................................
(Using Windows 10 Pro, Word 365 Pro)
The online Help for FileCopy Src, Dest says that it ... Copies a file from Src to Dest [but] does not work on a currently open file. Both ... files must be closed [by] the Close statement.
But the online help for Close, from link supplied on that page connects to help for Close for the Open statement, which says that it "Closes the file(s) previously opened with the" Open statement, not the FileCopy statement.
So it is that I'm stumped on what to do with this code, which will copy the first code module in the Document to a backup location, but not the second.
Pic#1: Info about what's supposedly going to be copied
Pic#2: Original error message without On Error
(I have no clue why all these blank lines. They're NOT in my Body.)
Please ignore all the OnError stuff for now.
When the second code module should have been copied, execution halted with error "File not found".
Sub BackupModules()
Dim prj As VBProject
Dim comp As VBComponent
Dim code As CodeModule
Set prj = ThisDocument.VBProject
Dim k As Integer, n As Integer
Dim Destination As String, Prefix As String
Prefix = "junk"
k = 0: n = 0
On Error GoTo x
For Each comp In prj.VBComponents
On Error GoTo x
k = k + 1
If comp.Type = vbext_ct_StdModule Then
n = n + 1
Destination = Prefix & n
MsgBox "Copying Standard module " & n & " of " & k & " components encountered: <" & comp.Name & "> to " _
& Destination & "; # lines: " & comp.CodeModule.CountOfLines
On Error GoTo x
FileCopy comp.Name, Destination
MsgBox "Success"
Close
Else
x: If Err.Number <> 0 Then: _
msg = "Error # " & Str(Err.Number) & " was generated by " _
& Err.Source & Chr(13) & Err.Description: _
MsgBox msg, , "Error", Err.HelpFile, Err.HelpContext: On Error GoTo 0: Close:
End If
Next
End Sub
Then I began experimenting (a LOT as you can see) with On Error Goto x being placed at various places (one at a time and then all, as shown) and the nasty-looking but syntactically and logically correct line that starts x: If Err... placed inside the Else block.
Pic#3: Error msg after using On Error
(FWIW, I just spotted Normal in the Err.Source part of the error message above. Online help says, "When an unexpected error occurs in your code, the Source property is automatically filled in. For errors in a standard module, Source contains the project name. For errors in a class module, Source contains a name with the project.class form." Indeed, the code is in a Module within the Normal Project.)
Pic#4: Line causing error that On Error did NOT trap
So what's wrong? I've tried everything I can think of. The only help I could find for Close did NOT mention its use with FileCopy. My Close usages caused no error but did Close close both the source and the destination file? Surely not. First use of FileCopy worked, files (probably) not closed, thus second use of FileCopy failed. Docs say using FileCopy on an open file will cause error.
On Error Goto x or to 0 is neither here nor there. That's why I said to ignore them at first.
The question is apparently "How do I close both files mentioned in FileCopy?"
P.S. Per opening blurb, I'm NOT gonna do this.
I suppose I could use Open ... For Input As File#1 and specify the Module's name, if it's readily available to code, and also Open ... For Output As File#2 for the destination, use a For loop to copy the number of lines, if available, and then Close both. But I hope I get a solution to my problem before I try that since SURELY FileCopy should work within a loop (and doesn't because of improper close).
Thanks to #TimWilliams, who tipped me off to Export, my final "Backup all modules" routine is quite simple.
Sub BackupModules()
Dim comp As VBComponent
Dim prj As VBProject: Set prj = ThisDocument.VBProject
Dim fso As Object: Set fso = CreateObject("Scripting.FileSystemObject")
Dim destPrefix As String: destPrefix = "C:\Users\Dov\Google Drive\Word\Modules\"
Dim destFilePath As String
For Each comp In prj.VBComponents
If comp.Type = vbext_ct_StdModule Then
destFilePath = destPrefix & comp.Name & " " & Year(Now) & " " & Month(Now) & " " & Day(Now)
Debug.Print "Copying Standard module <" & comp.Name & "> to <" & destFilePath & ">"
comp.Export (destFilePath)
Else
Debug.Print "Skipping component # " & k & ", <" & comp.Name & ">, type " & comp.Type
End If
Next
End Sub
I'm trying to diagnose an issue with VBScript FileSystemObject.CopyFile, it's reporting Error 53: File Not Found, but it has successfully copied the file! I've tried variations of the CopyFile command, with full destination name or just folder etc no difference, it always copies the file.
Worse, if I purposefully break it, by changing the source file name, I still get Error 53, which is the exact situation I'm trying to catch and report.
On Error Resume Next
'copy the officeUI xml to the Microsoft Office folder
filePath = profilePath & "\Microsoft\Office\"
if not WshFSO.FolderExists(filePath) then
WshFSO.CreateFolder filePath
end if
WshFSO.CopyFile scriptPath & "\Access.officeUI", filePath, True
'copy the foo client zip to \foo
filePath = profilePath & "\foo\"
if not WshFSO.FolderExists(filePath) then
WshFSO.CreateFolder filePath
end if
WshFSO.CopyFile localZip, filePath, True
if Err.Number <> 0 then
'catch that the copy failed
msg = "Failed to copy Foo, please report this to Help Desk." _
& vbCrLf & vbCrLf & "Citrix Server: " & WshNetwork.ComputerName _
& vbCrLf & "Error: " & err.Number & " - " & err.Description
WshShell.Popup msg, , "Foo Launcher", 16
Err.Clear
WScript.Quit
end if
The error is occuring on the final CopyFile call.
The problem was due to the Error handling logic in VBS, it was reporting the error number from an earlier line! The On Error Resume Next allowed the code to continue to run, but subsequent calls, even successful ones, did not overwrite the Err object. So when I did want to check the result, it picked up this previous error.
So from this I think that unless you want If Err.Number... everywhere is that before each 'block' of code you want to error check, clear the Err object first with Err.Clear.
I wish VBScript had On Error Goto like its cousin!
I use MZ Tools for Excel VBA at work, and I use their automatic error handler feature for most of my procedures because it allows me to easily put my contact information in the error message and automatically turn display alerts and screen updating back on. But if an error handler is used in VBA, it becomes difficult to locate the exact line of code that triggered the error, especially in a longish procedure. The default the only way I could figure out to use a custom error handler and get the line of code that triggered the error was to add these two lines to the end of the error handler (so that the problem line would be re-run with the default error handler after the custom error handler had done its work):
On Error GoTo 0
Resume
This works well if there is only one error handler; the user needs to click through one additional dialog box, but I can debug normally while maintaining the functionality built into my custom error handlers. But if both the calling routine and subroutine have distinct error handlers, the user starts to get a lengthy cascade of similar-looking dialog boxes. Precisely, I get 1 + n! dialogue boxes, where n is the number of levels of subroutines with error handlers.
The simplest way to illustrate the issue is when I run the first routine, I get 4 error messages instead of just 2:
Sub TstErrHndlr()
On Error GoTo TstErrHndlr_Error1
Call TstErrHndlrA
On Error GoTo 0
Exit Sub
TstErrHndlr_Error1:
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Call MsgBox("Error " & Err.Number & " (" & Err.Description _
& ") in procedure TstErrHndlr " _
& "of Module Create_Package." _
& " Contact [My Name] for assistance " _
& "(myemal#company.com, (123)456-7890)")
On Error GoTo 0
Resume
End Sub
Sub TstErrHndlrA()
On Error GoTo TstErrHndlrA_Error1
Dim X As Double
X = 1 / 0
On Error GoTo 0
Exit Sub
TstErrHndlrA_Error1:
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Call MsgBox("Error " & Err.Number & " (" & Err.Description _
& ") in procedure TstErrHndlrA " _
& "of Module Create_Package." _
& " Contact [My Name] for assistance " _
& "(myemal#company.com, (123)456-7890")
On Error GoTo 0
Resume
End Sub
After going through the code in debug mode, it seems like whenever a procedure is called by another procedure, whichever error handler was enabled in the calling function becomes the error handler that is enabled by the line On Error GoTo 0 no matter how many times it is repeated. I would like to know why VBA behaves this way, how to make it not behave this way, and/or if there is a better way to accomplish my goal of getting the line of code that triggered an error while using an error handler. I know that I could revert to the default error handler before a function is called with a new error handler (eg, On Error GoTo 0: Call TstErrHndlrA, but this makes for ugly confusing code, and will not handle errors that occur in the function call.
I suggest a restructure of your error handlers as follows
Add a Debug mode for your own use, which breaks in the error handler and offers possibility of a resume to see the line causing the error
Only raise the error popup at the level causing the actual error
Reset Application properties at the top level only
Lower level routine calls pass up unhandled errors
.
Option Explicit
' Debug Mode Flag (or you could use Conditional Compilation)
' Set to TRUE for developer mode debugging
Const DebugMode As Boolean = False ' True
Sub TstErrHndlr()
On Error GoTo TstErrHndlr_Error1
TstErrHndlrA
Exit Sub
TstErrHndlr_Error1:
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.DisplayAlerts = True
' display message if error is raised in this module
If Err.Source = Application.VBE.ActiveVBProject.Name Then
MsgBox "Error " & Err.Number & " (" & Err.Description & ")" & vbLf & _
"in procedure TstErrHndlr" & vbLf & _
"Contact [My Name] for assistance " & _
"(myemal#company.com, (123)456-7890)"
End If
' Break in Debug mode
If DebugMode Then
Debug.Assert False
Resume
End If
End Sub
Sub TstErrHndlrA()
On Error GoTo TstErrHndlrA_Error1
Dim X As Double
X = 1 / 0
Exit Sub
TstErrHndlrA_Error1:
' These should be handled at top level for unhandled errors only
' Application.Calculation = xlCalculationAutomatic
' Application.ScreenUpdating = True
' Application.DisplayAlerts = True
' display message if error is raised in this module
If Err.Source = Application.VBE.ActiveVBProject.Name Then
MsgBox "Error " & Err.Number & " (" & Err.Description & ")" & vbLf & _
"in procedure TstErrHndlrA" & vbLf & _
"Contact [My Name] for assistance " & _
"(myemal#company.com, (123)456-7890)"
End If
' Break in Debug mode
If DebugMode Then
Debug.Assert False
Resume
End If
' Pass unhandled errors up the tree
Err.Raise Err.Number, "TstErrHndlrA", Err.Description
End Sub
With Debug Mode OFF the user gets one popup identifying the error and routine it occurs in
With Debug Mode ON you also get a break in the routine causing the error, and the possibility of a Resume to the line causing the error. (Or use Ctrl-F9 to step over the Resume)
From MSDN's On Error page:
An "enabled" error handler is one that is turned on by an On Error statement;
an "active" error handler is an enabled handler that is in the process
of handling an error. If an error occurs while an error handler is
active (between the occurrence of the error and a Resume, Exit Sub,
Exit Function, or Exit Property statement), the current procedure's
error handler can't handle the error. Control returns to the calling
procedure. If the calling procedure has an enabled error handler, it
is activated to handle the error. If the calling procedure's error
handler is also active, control passes back through previous calling
procedures until an enabled, but inactive, error handler is found. If
no inactive, enabled error handler is found, the error is fatal at the
point at which it actually occurred. Each time the error handler
passes control back to a calling procedure, that procedure becomes the
current procedure. Once an error is handled by an error handler in any
procedure, execution resumes in the current procedure at the point
designated by the Resume statement.
So to answer "Why does VBA behave this way": Because that's the way they made it.
To make it not behave this way, you will have to (as you mentioned) disable the current error handler with before calling the sub/function.
Using ERL as #Rory mentioned will get you the exact line where your code fails, and you might possibly be able to utilize On Error Goto -1 in a broadly generic error trapping routine. It really comes down to being careful about calling other subs/functions, or having functions which can return an error code as their value (ie, bubbling the error up manually). For example, here's a function that returns the error as the value of the function rather than attempting to raise any kind of exception during the function call. You may also notice that some of the functions it calls might return errors as well.
Public Function SetTask(ByVal strHost As String, strUser As String, strDomain as String, strPass As String) As String
Dim service As Object
Dim rootFolder As Object
Dim taskDefinition As Object
Dim strCMD As String
Dim strResult As String
On Error GoTo TaskNotSet
SetTask = "Task Not Set"
'Open the firewall
strResult = OpenFirewall (strHost)
If strResult <> "Ok" Then
SetTask = "Error Opening Firewall (" & err.Number & ") " & err.Description
Exit Function
End If
Set service = CreateObject("Schedule.Service")
service.Connect strHost, strUser, strDomain, strPass
Set rootFolder = service.GetFolder("\")
Set taskDefinition = service.newtask(0)
taskDefinition.XmlText = TaskXML
Call rootFolder.RegisterTaskDefinition("Weekly VMC Inventory", taskDefinition, 6, , , 3)
'Close the firewall
strResult = CloseFirewall (strHost)
If strResult <> "Ok" Then
SetTask = "Error Closing Firewall (" & err.Number & ") " & err.Description
Exit Function
End If
SetTask = "Task Set"
Set taskDefinition = Nothing
Set rootFolder = Nothing
Set service = Nothing
Exit Function
TaskNotSet:
CloseFirewall (strHost)
SetTask = "Error Setting Task (" & err.Number & ") " & err.Description
Set taskDefinition = Nothing
Set rootFolder = Nothing
Set service = Nothing
End Function
This seems like the most basic of things. There are lots of examples on google, all of which I have put into my code and have gotten the same result.
I beleive I am missing something specific to the language, and it's really getting irritating.
Given
pathName$ = "..\..\images\" + artID + "\" + artNum + "\"
dirTest$ = "..\..\images\" + artID + "\"
If Dir$(pathName$ , ATTR_DIRECTORY) = "" Then
MsgBox "No Dir"
Else
MsgBox "Dir Found!"
End If
(everthing is dimmed correctly)
I have put msgbox's before pathName$ and right before the DIR call, but it fails when it gets to the test. I know for a fact that the dir doesn't exist in certain scenarios, but I would like to trap the error, not have the script crash on failing to find the dir.
I have tried DIR (path,16) DIR$(path,16) DIR (path$,16) DIR$(path$,16) as well as the ATTR_DIRECTORY key word.
How can I gracefully check the existence of a directory in Lotusscript?
The Dir$ command will generate the run-time error code 76 if the directory does not exist. So you can trap the run-time error by adding On Error 76 Resume Next to your code:
pathName$ = "..\..\images\" + artID + "\" + artNum + "\"
dirTest$ = "..\..\images\" + artID + "\"
On Error 76 Resume Next
If Dir$(pathName$ , ATTR_DIRECTORY) = "" Then
MsgBox "No Dir"
Else
MsgBox "Dir Found!"
End If
Inspiration: http://searchdomino.techtarget.com/tip/Finding-files-and-directories-with-LotusScript
I think the better solution is to test a variant with the Dir$ function result. That's because if the dir path is completely wrong the error 76 gives back a variant containing the error. So it should be managed, like this:
`
On Error 76 GoTo PathNotValid
result = Dir$(dirPath$,16)
If result <>"" then
MessageBox dirPath$ + " found :) "
Else
MessageBox dirPath$ + " NOT found :( "
End if
End:
Exit Sub
PathNotValid:
MessageBox dirPath$ + " IS NOT VALID !!!"
result = ""
Resume Next
`
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I’d like to remove a node from my xml file using VBA in MS Project 2007.
Should be so easy but I can’t get it running.
Here is my XML
<config id="config" ConfigSaveDate="2011-03-31 21:32:55" ConfigSchemaVersion="1.02">
<Custom>
</Custom>
<Program>
<DateFormat>yyyy-mm-dd hh:mm:ss</DateFormat>
</Program>
<ProjectFile ProjectFileName="projectfile1.mpp">
<RevisionNumber>201</RevisionNumber>
<FileName>projectfile1.mpp</FileName>
<LastSaveDate>2011-03-23 16:45:19</LastSaveDate>
</ProjectFile>
<ProjectFile ProjectFileName="projectfile2bedeleted.mpp">
<RevisionNumber>115</RevisionNumber>
<FileName>projectfile2bedeleted.mpp</FileName>
<LastSaveDate>2011-03-31 21:12:55</LastSaveDate>
</ProjectFile>
<ProjectFile ProjectFileName="projectfile2.mpp">
<RevisionNumber>315</RevisionNumber>
<FileName>projectfile2.mpp</FileName>
<LastSaveDate>2011-03-31 21:32:55</LastSaveDate>
</ProjectFile>
</config>
Here is my VBA code
Function configProjListDelete(configPath As String, ProjFiles As Variant) As Integer
' This function shall delete <ProjectFile> tags from the config.xml
' and shall delete coresponding project xml files from HD
' It shall return number of deleted files
' configPath is the path to the xml folder
' ProjFiles is an array of file names of to be deleted files in above mentioned folder
Dim xml As MSXML2.DOMDocument
Dim RootElem As MSXML2.IXMLDOMElement
'Dim cxp1 As CustomXMLPart
Dim delNode As MSXML2.IXMLDOMNode ' XmlNode 'MSXML2.IXMLDOMElement
Dim fSuccess As Boolean
Dim ProjectFileList As MSXML2.IXMLDOMElement
Dim fn As Variant 'file name in loop
Dim i As Integer
Dim delCnt As Integer
If Not FileExists(configPath) Then
' given configFile doesn't exist return nothing
Debug.Print " iven config file doesn't exist. File: " & configPath
GoTo ExitconfigProjListDelete
End If
'TODO: Catch empty ProjectFiles
' Initialize variables
Set xml = New MSXML2.DOMDocument
On Error GoTo HandleErr
' Load the XML from disk, without validating it.
' Wait for the load to finish before proceeding.
xml.async = False
xml.validateOnParse = False
fSuccess = xml.Load(configPath)
On Error GoTo 0
' If anything went wrong, quit now.
If Not fSuccess Then
GoTo ExitconfigProjListDelete
End If
Set RootElem = xml.DocumentElement
Debug.Print "- " & xml.getElementsByTagName("ProjectFile").Length & " ProjectFiles in config."
i = 0
delCnt = 0
' Loop through all ProjectFiles
For Each ProjectFileList In xml.getElementsByTagName("ProjectFile")
' check if each project file name is one of the files to be deleted
For Each fn In ProjFiles
If fn = ProjectFileList.getElementsByTagName("FileName").NextNode.nodeTypedValue Then
Debug.Print fn & " shall be deleted"
' remove it from the document
' here I'm struggeling!
'#################################################
' How to delete the node <ProjectFile> and its childNodes?
Set delNode = ProjectFileList.ParentNode
xml.DocumentElement.RemoveChild (ProjectFileList) ' Error: 438 rough translation: "Object doesn't support this methode"
' This is all I've tried, but nothing works
'===========================================
'RootElem.RemoveChild (delNode)
'xml.RemoveChild (delNode)
'RootElem.RemoveChild (ProjectFileList.SelectSingleNode("ProjectFile"))
'ProjectFileList.ParentNode.RemoveChild (ProjectFileList.ChildNodes(0))
'Set objParent = datenode.ParentNode
'xmldoc.DocumentElement.RemoveChild (objParent)
'Set ProjectFileList = Empty
delCnt = delCnt + 1
End If
Next fn
i = i + 1
Next ProjectFileList
' Save XML File
If checkAppPath("Trying to update config file.") Then
xml.Save CustomProperty("XMTMLMonitoring_AppPath") & "\" & m2w_config("SubFolder") & "\" & m2w_config("SubFolderData") & "\" & m2w_config("XMLConfigFileName")
Debug.Print " - Config has been updated and saved."
Else
MsgBox "Config data not exported to web." & Chr(10) & "Folder: '" & CustomProperty("XMTMLMonitoring_AppPath") & "\" & m2w_config("SubFolder") & "\" & m2w_config("SubFolderData") & Chr(10) & "doesn't exist. ", vbOKOnly, HEADLINE
End If
Set xml = Nothing
configProjListDelete = delCnt
ExitconfigProjListDelete:
Exit Function
HandleErr:
Debug.Print "XML File reading error " & Err.Number & ": " & Err.DESCRIPTION
MsgBox "Error " & Err.Number & ": " & Err.DESCRIPTION
On Error GoTo 0
End Function
I’d be glad to get some help!
Do you know about XPath? From the painful looks of your code, you do not. Instead of using a long combination of barbaric DOM methods to access the node you need, you should save yourself a lot of pain and just use an XPath to access it in one line.
If I understand correctly what you're trying to do, then something like the following can replace your entire double loop, from i=0 to Next ProjectFileList:
For i = LBound(ProjFiles) To UBound(ProjFiles)
Set deleteMe = XML.selectSingleNode( _
"/config/ProjectFile[#ProjectFileName='" & ProjFiles(i) & "']")
Set oldChild = deleteMe.parentNode.removeChild(deleteMe)
Next i
where the thing in "quotes" is an XPath. Hope this helps.
As a side note, it seems inefficient, confusing, and error-prone to have a ProjectFileName attribute and a FileName element containing the exact same information in your XML file. What's up with that?