Python script suddenly no longer running when being called by VBA - python

EDIT: I made an error when posting below in saying that it worked when running from terminal (I must have tested earlier.) This problem was solved by using the python.exe program in the environment (env) folder pycharm installed instead of the stand alone installation I made originally.
EDIT #2: The problem is persisting again without having changed any of the VBA or python script. (still using the pycharm environment folder python.exe)
I have a VBA sub that creates a WScript.Shell object and then executes a python script which was working fine for the last few weeks. After continuing to build on the code in the python script (salesHist.py) the python script no longer runs correctly and produces an exit code "1". When running the script through pyCharm or console the script fully executes correctly with exit code 0.
I've tried some different variations of the code which are included below. Directories for python.exe and salesHist.py are both correct and do not contain any spaces (which I know is a common error.)
Also references added:
Visual Basic for Applications,
Microsoft Excel 16.0 Object Library,
OLE Automation,
Microsoft Office 16.0 Object Library,
Microsoft HTML Object Library,
Microsoft Scripting Runtime,
Microsoft XML, v3.0,
Windows Script Host Object Model
Sub RunPythonScript(pyScript As String)
'Declare varables
Dim objShell As Object
Dim PythonExe, PythonScript As String
Dim waitOnReturn As Boolean, windowStyle As Integer, retVal As Long
waitOnReturn = True
windowStyle = 0
'Create new object shell
Set objShell = VBA.CreateObject("WScript.Shell")
'Tried Set objShell CreateObject("WScript.Shell")
'Provide the file path to the Python Exe
PythonExe = "C:\Users\steve.levy\AppData\Local\Programs\Python\Python37-32\python.exe"
'PC1: C:\Users\Steven\AppData\Local\Programs\Python\Python37-32\python.exe
'PC2: C:\Users\steve.levy\AppData\Local\Programs\Python\Python37-32\python.exe
'make sure you use triple quotes if there is a space in the file path name. single quotes are ok if not
'Procide the file path to the Python Script
PythonScript = "C:\Users\steve.levy\Documents\elberon\api\" & pyScript
'PC1: C:\Users\Steven\Documents\api\
'PC2: C:\Users\steve.levy\Documents\elberon\api\
Debug.Print (PythonExe)
Debug.Print (PythonScript)
'Run the Python Script
'Tried: Call objShell.Run(PythonExe & " " & PythonScript, 0, True)
'Tried: retVal = objShell.Run(PythonExe & " " & PythonScript, 0, True)
retVal = objShell.Run("C:\Users\steve.levy\AppData\Local\Programs\Python\Python37-32\python.exe C:\Users\steve.levy\Documents\elberon\api\salesHist.py", 0, True)
If retVal = 0 Then
'Do Nothing
Else
MsgBox "Script could not run. Program exited with error code " & retVal & "."
End If
End Sub
Sub pyscr()
Call executePython.RunPythonScript("salesHist.py")
End Sub
Program exists and message box appears:
"Script could not run. Program exited with error code 1."

Related

VB .NET 4.5: Running python script through Shell() returns "File not found"

Running the following snippet yields "System.IO.FileNotFoundException: File not found"
Sub Main()
Dim scriptPath As String = "D:\Programs\Tester.py"
Dim pythonPath As String = "D:\anaconda3\python.exe"
If IO.File.Exists(scriptPath) Then
Shell(pythonPath + " " + scriptPath)
End If
End Sub
I am at a loss as to why this happens, because there is no problem in finding the file for the If-statement, however, as soon as it has to get executed it "does not exist". My original code featured a python script running as a process (as below), however, for some reason this stopped working: it runs without any errors, the script just doesn't get executed. The paths are copied from each file's "Properties" so I know they are still correct.
...
Dim scriptPath As String = "D:\Programs\Tester.py"
Dim pythonExePath As String = "D:\anaconda3\python.exe"
Dim gzipProcess As New Process()
Dim gzipStartInfo As New ProcessStartInfo(pythonExePath, scriptPath)
gzipStartInfo.UseShellExecute = False
gzipProcess.StartInfo.CreateNoWindow = True
gzipProcess.StartInfo = gzipStartInfo
gzipProcess.Start()
...
If I manually open a shell I can simply run the script as "python scriptPath" and it executes normally.
Any suggestions on how to fix this?

Hide the cmd window while executing the python code from vba [duplicate]

I'm trying to execute this simple test script, but a command shell window is appearing after I execute the script.:
Set objShell = WScript.CreateObject("WScript.Shell")
strCommand = "cmd /C tasklist"
Set objExecObject = objShell.Exec(strCommand)
wscript.echo "Test"
How can I prevent it from showing up?
Update
I was able to improve it with this code change:
strCommand = "cmd /C /Q tasklist"
Now the window only shows up for a split second. But I don't want it to show up at all.
You're always going to get a window flash with Exec(). You can use Run() instead to execute the command in a hidden window. But you can't directly capture the command's output with Run(). You'd have to redirect the output to a temporary file that your VBScript could then open, read, and delete.
For example:
With CreateObject("WScript.Shell")
' Pass 0 as the second parameter to hide the window...
.Run "cmd /c tasklist.exe > c:\out.txt", 0, True
End With
' Read the output and remove the file when done...
Dim strOutput
With CreateObject("Scripting.FileSystemObject")
strOutput = .OpenTextFile("c:\out.txt").ReadAll()
.DeleteFile "c:\out.txt"
End With
The FileSystemObject class has methods like GetSpecialFolder() to retrieve the path of Windows temp folder and GetTempName() to generate a temporary filename that you can use instead of hardcoding an output filename as I've done above.
Also note that you can use the /FO CSV argument with tasklist.exe to create a CSV file which should make parsing it much easier.
Finally, there are VBScript "native" ways to retrieve the list of running processes. WMI's Win32_Process class, for example, can do this without the need for Run/Exec.
Edit:
For the sake of completeness, I should mention that your script can relaunch itself in a hidden console window where you can run Exec() silently. Unfortunately, this hidden console window will also hide your output from functions like WScript.Echo(). Aside from that, however, you probably won't notice any differences running your script under cscript vs wscript. Here's an example of this method:
' If running under wscript.exe, relaunch under cscript.exe in a hidden window...
If InStr(1, WScript.FullName, "wscript.exe", vbTextCompare) > 0 Then
With CreateObject("WScript.Shell")
WScript.Quit .Run("cscript.exe """ & WScript.ScriptFullName & """", 0, True)
End With
End If
' "Real" start of script. We can run Exec() hidden now...
Dim strOutput
strOutput = CreateObject("WScript.Shell").Exec("tasklist.exe").StdOut.ReadAll()
' Need to use MsgBox() since WScript.Echo() is sent to hidden console window...
MsgBox strOutput
Of course, if your script expects command-line parameters, those would need to be forwarded when relaunching your script as well.
Edit 2:
Yet another possibility is to use the Windows clipboard. You can pipe the output of your command to the clip.exe utility. Then, retrieve the text via any number of available COM objects that can access the contents of the clipboard. For example:
' Using a hidden window, pipe the output of the command to the CLIP.EXE utility...
CreateObject("WScript.Shell").Run "cmd /c tasklist.exe | clip", 0, True
' Now read the clipboard text...
Dim strOutput
strOutput = CreateObject("htmlfile").ParentWindow.ClipboardData.GetData("text")
You can use .Exec() method, without console window flash, temp files and unexpected WScript.Echo output muting. The method is slightly tricky, and requires to launch secondary linked script, so I added the comments:
Option Explicit
Dim objDummy, strSignature, objPrimary, objSecondary, objContainer, objWshShell, objWshShellExec, strResult
' this block is executed only in the secondary script flow, after primary script runs cscript
If WScript.Arguments.Named.Exists("signature") Then
' retrieve signature string from argument
strSignature = WScript.Arguments.Named("signature")
Do
' loop through all explorer windows
For Each objContainer In CreateObject("Shell.Application").Windows
' check if the explorer's property with signature name contains the reference to the live script
If ChkVBScriptTypeInfo(objContainer.getProperty(strSignature)) Then
Exit Do
End If
Next
WScript.Sleep 10
Loop
' create shell object within secondary script
Set objWshShell = CreateObject("WScript.Shell")
' retrieve the primary script me object reference from explorer's property with signature name
Set objPrimary = objContainer.getProperty(strSignature)
' quit explorer window to release memory as it's no longer needed
objContainer.Quit
' assign the secondary script me object to the primary script's variable
Set objPrimary.objSecondary = Me
' emtpy loop while primary script is working
Do While ChkVBScriptTypeInfo(objPrimary)
WScript.Sleep 10
Loop
' terminate secondary
WScript.Quit
End If
' the code below is executed first in the primary script flow
' create signature string
strSignature = Left(CreateObject("Scriptlet.TypeLib").Guid, 38)
' create new hidden explorer window as container to transfer a reference between script processes
Set objContainer = GetObject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}")
' put this script's me object reference into explorer's property
objContainer.putProperty strSignature, Me
' launch new secondary process of the same script file via cscript.exe with hidden console window, providing signature string in named argument to identify host script
CreateObject("WScript.Shell").Run ("""" & Replace(LCase(WScript.FullName), "wscript", "cscript") & """ //nologo """ & WScript.ScriptFullName & """ ""/signature:" & strSignature & """"), 0
' wait until secondary script has been initialized and put his me object into this script variable
Do Until ChkVBScriptTypeInfo(objSecondary)
WScript.Sleep 10
Loop
' here is your code starts...
' create exec object within hidden console window of secondary script, execute cmd instruction
Set objWshShellExec = objSecondary.objWshShell.Exec("%comspec% /c tasklist")
' read cmd output
strResult = objWshShellExec.StdOut.ReadAll()
WScript.Echo strResult
' ...
' utility check if me object is live
Function ChkVBScriptTypeInfo(objSample)
On Error Resume Next
If TypeName(objSample) <> "VBScriptTypeInfo" Then
ChkVBScriptTypeInfo = False
Exit Function
End If
ChkVBScriptTypeInfo = True
End Function
UPDATE
I've slightly reworked the code to make it more straightforward:
Option Explicit
Dim strCmd, strRes, objWnd, objParent, strSignature
If WScript.Arguments.Named.Exists("signature") Then WshShellExecCmd
strCmd = "%comspec% /c tasklist"
RunCScriptHidden
WScript.Echo strRes
Sub RunCScriptHidden()
strSignature = Left(CreateObject("Scriptlet.TypeLib").Guid, 38)
GetObject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}").putProperty strSignature, Me
CreateObject("WScript.Shell").Run ("""" & Replace(LCase(WScript.FullName), "wscript", "cscript") & """ //nologo """ & WScript.ScriptFullName & """ ""/signature:" & strSignature & """"), 0, True
End Sub
Sub WshShellExecCmd()
For Each objWnd In CreateObject("Shell.Application").Windows
If IsObject(objWnd.getProperty(WScript.Arguments.Named("signature"))) Then Exit For
Next
Set objParent = objWnd.getProperty(WScript.Arguments.Named("signature"))
objWnd.Quit
objParent.strRes = CreateObject("WScript.Shell").Exec(objParent.strCmd).StdOut.ReadAll()
WScript.Quit
End Sub
BTW, here is VBScript "multithreading" implementation that uses the same container approach.
Some great suggestions are listed above. I'd like to make one more suggestion which is more of a workaround. You can use Sysinternals Desktops (a free program) to run your macro on another desktop on your same machine. That way the flashing can all happen on its own desktop and won't interrupt your work.
I use Sysinternals PSEXEC
https://learn.microsoft.com/sv-se/sysinternals/downloads/psexec
Created a Batch-file (in the same folder as the vbs and exe-file) that runs the script as system user.
I can not Access the user profile and I need to be local Admin but when i run the script without interaction with the desktop it will hide all annoying popups.
Run Script as system without interaction with desktop
"%~dp0PsExec.exe" -s wscript.exe "%~dp0MyScript.vbs"
Run Script as system with interaction with desktop
"%~dp0PsExec.exe" -s -i wscript.exe "%~dp0MyScript.vbs"
To hide the command line windows in VBscipt is use Run in WshShell Object
Then to get the result you can send this result to text file in %temp%
Then read this result with FileSystemObject
Set Sh = CreateObject("WScript.Shell")
tFile=Sh.ExpandEnvironmentStrings("%Temp%")&"\t.txt"
Sh.Run "cmd.exe /C tasklist > """&tFile&""" ",0,False
Wscript.echo CreateObject("Scripting.FileSystemObject").openTextFile(tFile).readAll()
OR
If StrComp(right(WScript.FullName,11),"wscript.exe",1) = 0 Then '' get only wscript.exe from "c:\windows\system32\wscript.exe" to compere with wscript.exe
WScript.Quit CreateObject("WScript.Shell").Run("cscript.exe """ & WScript.ScriptFullName & """", 0, False)
End If
MsgBox CreateObject("WScript.Shell").Exec("cmd.exe /c tasklist /v /fi ""imagename EQ explorer*"" /FO LIST | FIND ""PID:""").StdOut.ReadAll()
An alternative to using to windows scripting host is here: Run a batch program(.bat) through a Visual Basic 6.0
It runs a program and captures its screen output. It works for me in VB6, but not in VBA (hangs at WaitForSingleObject, don't know why).
After trying the main solutions without success, I was able to solve my problem with the following code:
With CreateObject("WScript.Shell")
.Run "cmd /c start /b tasklist.exe > c:\out.txt", 0, True
End With
The real deal was the "/b" as the console help display:
START ["title"] [/D path] [/I] [/MIN] [/MAX] [/SEPARATE | /SHARED]
[/LOW | /NORMAL | /HIGH | /REALTIME | /ABOVENORMAL | /BELOWNORMAL]
[/NODE <NUMA node>] [/AFFINITY <hex affinity mask>] [/WAIT] [/B]
[command/program] [parameters]
"title" Title to display in window title bar.
path Starting directory.
B Start application without creating a new window. The
application has ^C handling ignored. Unless the application
enables ^C processing, ^Break is the only way to interrupt
the application.

is python usable in MS Access without linking to the database

New here with a problem I am trying to work out.
I was wondering if I had to connect python to my Access database for it to run a python file.
Basically, I want to click a button in Access and have it fire a python code to open a file dialog.
I would also like for the following to work:
Situation 1: can copy a path into an Access form
Situation 2: can open a merge which is is already linked to access
So, can I do something like that without connecting my python code to access?
If the python code does not need to pull information from the Access tables then you don't need the script to connect to the Access database via pyodbc (or anything else). Given a test script ...
# C:\__tmp\get_path.py
print(r"C:\Users\Gord\Desktop\foo.pdf")
... the following VBA code ...
Sub getPathFromPython()
Const pyInterpreter = "C:\Users\Gord\AppData\Local\Programs\Python\Python38-32\python.exe"
Const pyScript = "C:\__tmp\get_path.py"
Dim objShell As New WshShell, objExec As Object
Set objExec = objShell.Exec(pythonPath & " " & pyScript)
Dim stdout As String
stdout = objExec.stdout.ReadAll
Debug.Print "Python code returned:" & vbCrLf & stdout
End Sub
... prints this in the Immediate Window of the VBA editor:
Python code returned:
C:\Users\Gord\Desktop\foo.pdf

Run python script from VBA

I have tried several solutions to run python script out of VBA.
My current solution that still doesnt work
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim windowStyle As Integer: windowStyle = 1
Dim waitOnReturn As Boolean: waitOnReturn = True
wsh.Run "C:\Python33\python.exe C:\Users\***\Documents\Promo models\get_sku_data.py", windowStyle, waitOnReturn
Any ideas?
xlwings claims to make python from excel very easy:
Sub RandomNumbers()
RunPython ("import mymodule; mymodule.rand_numbers()")
End Sub
To make this run, just import the VBA module xlwings.bas in the VBA editor (Open the VBA editor with Alt-F11, then go to File > Import File... and import the xlwings.bas file. ). It can be found in the directory of your xlwings installation.
Finally I ended up with creation of .exe files instead of trying to call scripts itself.
So, I applied cx_Freeze, created .exe files and then called them as
Script_path1 = "C:\Users\" + Environ("Username") & "\Documents\Promo_models\get_sku_data\gui_changed.exe"
wsh.Run Script_path1, windowStyle, waitOnReturn
Here are a couple options to try.
RetVal = Shell("<full path to python.exe> " & "<full path to your python script>")
Or if the python script is in the same folder as the workbook, then you can try :
RetVal = Shell("<full path to python.exe> " & "ActiveWorkBook.Path & \<python script name>")

VBA shell script set environment variable before execution

I'm trying to run a VBA module that uses a shell script to run an ipython notebook (using runipy) before going on to do other things, but my ipython notebook script requires inputs, which are setup using environment variables.
Here's the VBA I have so far:
rundate = Range("b1").Value
shellScript = "runipy C:\argstest.ipynb"
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim windowStyle As Integer: windowStyle = 1
Set wshSystemEnv = wsh.Environment("SYSTEM")
wshSystemEnv("CURRWK") = rundate
If I then run:
MsgBox (wshSystemEnv("CURRWK"))
The result is correct:
2015-04-17
However, if I continue on and exec my shellscript, it isn't recognizing this variable
Set WshShellExec = wsh.exec(shellScript)
Range("a1").Value = WshShellExec.StdErr.ReadAll
shows me that when python runs it gets a key error:
KeyError: 'CURRWK'
If I remove the VBA and run this straight from the command line, I would do it like this:
set CURRWK=2015-04-17
runipy C:\Python27\Programs\ipython\ACT\argstest.ipynb
How do I get the shell created in the VBA to create the new environment variable so the python script can see it when it executes?
Thanks!
Have you tried Process instead of SYSTEM? Your VBA may not have the rights to modify the system-level variables. ("But it can read it back after setting it," you say. True, but your script may have registry virtualization enabled.) Process most closely emulates the command line SET statement in that the change is only for the current process and child processes and is not persisted.

Categories

Resources