call vba function from python in already open excel file - python

I am trying to run vba macro from Python but the excel file which has macro is already open. So I want to not open it again as another instance.
I have tried win32com code - however it reopens another instance of the same file and makes changes in that instance and ask me to save as second excel file. But i want to call macro in already open file. and after calling that macro leave the excel file open.
this is the path where File is located - its a private shared drive
\nplofnp0001a\gtonnnfs10912400\CPM Other\PL Estimate\PL Estimate Sheets\DAILY PL ESTIMATE W NRPD RV2 v6_YK_v2.xlsm
folder_xlfile = '\\\\nplofnp0001a\gtonnnfs10912400\CPM Other\PL Estimate\PL Estimate Sheets'
xlfile = '\\DAILY PL ESTIMATE W NRPD RV2 v6_YK_v2.xlsm'
Filename=folder_xlfile + xlfile
import win32com.client
xl=win32com.client.DispatchEx("Excel.Application")
#wb = xl.Workbooks.Open(Filename)
xl.Application.Run(Filename + "!Module2.CopySummaryData")
#xl.Application.Quit()
del xl
Expected Result - run vba function in already open excelfile.
Actual Result - just keeps running without error message. I guess code hangs.

xl.Application.Run(r"'DAILY PL ESTIMATE W NRPD RV2 v6_YK_v2.xlsm'!Module2.CopySummaryData")

Related

How to save an excel using pywin32?

I am trying to save an excel file generated by another application that is open. i.e the excel application is in the foreground. This file has some data and it needs to be saved i.e written into the disk.
In other words, I need to do an operation like File->SaveAs.
Steps to reproduce:
Open an Excel Application. This will be shown as Book1 - Excel in the title by default
Write this code and run
import win32com.client as win32
app = win32.gencache.EnsureDispatch('Excel.Application')
app.Workbooks(1).SaveAs(r"C:\Users\test\Desktop\test.xlsx")
app.Application.Quit()
Error -
Traceback (most recent call last):
File "c:/Users/test/Downloads/automate_excel.py", line 6, in <module>
ti = disp._oleobj_.GetTypeInfo()
pywintypes.com_error: (-2147418111, 'Call was rejected by callee.', None, None)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "c:/Users/test/Downloads/automate_excel.py", line 6, in <module>
app = win32.gencache.EnsureDispatch('Excel.Application')
File "C:\Users\test\AppData\Local\Programs\Python\Python38\lib\site-packages\win32com\client\gencache.py", line 633, in EnsureDispatch
raise TypeError(
TypeError: This COM object can not automate the makepy process - please run makepy manually for this object
There could be many sources for your problem so I would apreciate if you shared further code. The second error can for example occur when you are running multiple instances of the line excel = win32.gencache.EnsureDispatch('Excel.Application') for example in a for loop .
Also make sure to have a version of excel that is fully activated and licensed .
This is working for me (on python==3.9.8 and pywin32==305). You'll see that the first line is a different than yours, but I think that's really it.
In the course of this we kept getting Attribute Errors for the Workbook or for setting DisplayAlerts. We found (from this question: Excel.Application.Workbooks attribute error when converting excel to pdf) that if Excel is in a loop (for example, editing a cell or has a pop-up open) then you will get an error. So, be sure to click enter out of a cell so that you aren't editing it.
import win32com.client as win32
savepath = 'c:\\my\\file\\path\\test\\'
xl = win32.Dispatch('Excel.Application')
wb = xl.Workbooks['Book1']
wb.DisplayAlerts = False # helpful if saving multiple times to save file, it means you won't get a pop-up for overwrite and will default to save it.
filename = 'new_xl.xlsx'
wb.SaveAs(savepath+filename)
wb.Close()
xl.Quit()
edit: add pywin32 version, include some more tips
This is the version that worked for me based on #scotscotmcc's answer. The issue was with the cell which was in edit mode while I was running the program. Make sure you hit enter in the current cell and come out of the edit mode in excel.
import win32com.client as win32
import random
xl = win32.Dispatch('Excel.Application')
wb = xl.Workbooks['Book1']
wb.SaveAs(r"C:\Users\...\Desktop\Form"+str(random.randint(0,1000))+".xlsx")
wb.Close()
xl.Quit()

workbook save failing, not sure why

I apologize for the length of this. I am a relative Neophyte to Excel VBA and even more junior with Python. I have run into an issue with an error that occasionally occurs in python using OpenPyXl (just trying that for the first time).
Background: I have a series of python scripts (12) running and querying an API to gather data and populate 12 different, though similar, workbooks. Separately, I have a equal number of Excel instances periodically looking for that data and doing near-real-time analysis and reporting. Another python script looks for key information to be reported from the spreadsheets and will text it to me when identified. The problem seems to occur between the data gathering python scripts and a copy command in the data analysis workbooks.
The way the python data gathering scripts "talk" to the analysis workbooks is via the sheets they build in their workbooks. The existing vba in the analysis workbooks will copy the data workbooks to another directory (so that they can be opened and manipulated without impacting their use by the python scripts) and then interpret and copy the data into the Excel analysis workbook. Although I recently tested a method to read the data directly from those python-created workbooks without opening them, the vba will require some major surgery to convert to that method and is likely not going to happen soon.
TL,DR: There are data workbooks and analysis workbooks. Python builds the data workbooks and the analysis workbooks use VBA to copy the data workbooks to another directory and load specific data from the copied data workbooks. There is a one-to-one correspondence between the data and analysis workbooks.
Based on the above, I believe that the only "interference" that occurs with the data workbooks is when the macro in the analysis workbook copies the workbook. I thought this would be a relatively safe level of interference, but it apparently is not.
The copy is done in VBA with this set of commands (the actual VBA sub is about 500 lines):
fso.CopyFile strFromFilePath, strFilePath, True
where fso is set thusly:
Set fso = CreateObject("Scripting.FileSystemObject")
and the strFromFilePath and strFilePath both include a fully qualified file name (with their respective paths). This has not generated any errors on the VBA side.
The data is copied about once a minute (though it varies from 40 seconds to about 5 minutes) and seems to work fine from a VBA perspective.
What fails is the python side about 1% of the time (which is probably 12 or fewer times daily. While that seems small, the associated data capture process halts until I notice and restart it. This means anywhere from 1 to all 12 of the data capture processes will fail at some point each day.
Here is what a failure looks like:
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
monitor('DLD',1,13,0)
File "<string>", line 794, in monitor
File "C:\Users\abcd\AppData\Local\Programs\Python\Python39\lib\site-packages\openpyxl\workbook\workbook.py", line 407, in save
save_workbook(self, filename)
File "C:\Users\abcd\AppData\Local\Programs\Python\Python39\lib\site-packages\openpyxl\writer\excel.py", line 291, in save_workbook
archive = ZipFile(filename, 'w', ZIP_DEFLATED, allowZip64=True)
File "C:\Users\abcd\AppData\Local\Programs\Python\Python39\lib\zipfile.py", line 1239, in __init__
self.fp = io.open(file, filemode)
PermissionError: [Errno 13] Permission denied: 'DLD20210819.xlsx'
and I believe it occurs as a result of the following lines of python code (which comes after a while statement with various if conditions to populate the worksheets). The python script itself is about 200 lines long:
time.sleep(1) # no idea why wb.save sometimes fails; trying a delay
wb.save(FileName)
Notice, I left in one of the attempts to correct this. I have tried waiting as much as 3 seconds with no noticeable difference.
I admit I have no idea how to detect errors thrown by OpenPyXl and am quite unskilled at python error handling, but I had tried this code yesterday:
retries = 1
success = False
while not success and retries < 3:
try:
wb.save
success = True
except PermissionError as saveerror:
print ('>>> Save Error: ',saveerror)
wait = 3
print('=== Waiting %s secs and re-trying... ===' % wait)
#sys.stdout.flush()
time.sleep(wait)
retries += 1
My review of the output tells me that the except code never executed while testing the data capture routine over 3000 times. However, the "save" also never happened so the analysis spreadsheets did not receive any information until later when the python code saved the workbook and closed it.
I also tried adding a wb.close after setting the success variable to true, but got the same results.
I am considering either rewriting the VBA to try to grab the data directly from the unopened data workbooks without first copying them (which actually sounds more dangerous) or using an external synching tool to copy them outside of VBA (which could potentially cause exactly the same problem).
Does anyone have an idea of what may be happening and how to address it? It works nearly all the time but just fails several times a day.
Can someone help me to better understand how to trap the error thrown by OpenPyXl so that I can have it retry rather than just abending?
Any suggestions are appreciated. Thank you for reading.
Not sure if this is the best way, but the comment from simpleApp gave me an idea that I may want to use a technique I used elsewhere in the VBA. Since I am new to these tools, perhaps someone can suggest a cleaner approach, but I am going to try using a semaphore file to signal when I am copying the file to alert the python script that it should avoid saving.
In the below I am separating out the directory the prefix and the suffix. The prefix would be different for each of the 12 or more instances I am running and I have not figured out where I want to put these files nor what suffix I should use, so I made them variables.
For example, in the VBA I will have something like this to create a file saying currently available:
Dim strSemaphoreFolder As String
Dim strFilePrefix As String
Dim strFileDeletePath As String
Dim strFileInUseName As String
Dim strFileAvailableName As String
Dim strSemaphoreFileSuffix As String
Dim fso As Scripting.FileSystemObject
Dim fileTemp As TextStream
Set fso = CreateObject("Scripting.FileSystemObject")
strSemaphoreFileSuffix = ".txt"
strSemaphoreFolder = "c:\temp\monitor\"
strFilePrefix = "RJD"
strFileDeletePath = strSemaphoreFolder & strFilePrefix & "*" & strSemaphoreFileSuffix
' Clean up remnants from prior activities
If Len(Dir(strFileDeletePath)) > 0 Then
Kill strFileDeletePath
End If
' files should be gone
' Set the In-use and Available Names
strFileInUseName = strFilePrefix & "InUse" & strSemaphoreFileSuffix
strFileAvailableName = strFilePrefix & "Available" & strSemaphoreFileSuffix
' Create an available file
Set fileTemp = fso.CreateTextFile(strSemaphoreFolder & strFileAvailableName, True)
fileTemp.Close
' available file should be there
Then, when I am about to copy the file, I will briefly change the filename to indicate that the file is in use, perform the potentially problematic copy and then change it back with something like this:
' Temporarily name the semaphore file to "In Use"
Name strSemaphoreFolder & strFileAvailableName As strSemaphoreFolder & strFileInUseName
fso.CopyFile strFromFilePath, strFilePath, True
' After copying the file name it back to "Available"
Name strSemaphoreFolder & strFileInUseName As strSemaphoreFolder & strFileAvailableName
Over in the Python script, before I do the wb.save command, I will insert a check to see whether the file indicates that it is available or in use with something like this:
prefix = 'RJD'
directory = 'c:\\temp\\monitor\\'
suffix = '.txt'
filepathname = directory + prefix + 'Available' + suffix
while not (os.path.isfile(directory + prefix + 'Available' + suffix)):
time.sleep(1)
wb.save
Does this seem like it would work?
I am thinking that it should avoid the failure if I have properly identified it as an attempt to save the file in the Python script while the VBA script is telling the operating system to copy it.
Thoughts?
afterthoughts:
Using the technique I described, I probably need to create the "Available" semaphore file in the Python script and simply assume it will be there in the VBA script since the Python script is collecting the data and may be doing so before the VBA is even started.
A better alternative may be to simply check for the existence of the "In Use" file which will never be there unless the VBA wants it there, like this:
while (os.path.isfile(directory + prefix + 'InUse' + suffix)):
time.sleep(1)
wb.save

Start python script from excel with input

I have a Python script (gui.py - a calculation program, written by somebody else). I try to run this from excel with input data from the same excel file. It works fine when I start the script as:
objShell.Run PythonExe & PythonScript3
In the python script I used the following to get f.ex data from H5 cell, works also fine:
import openpyxl
wb = openpyxl.load_workbook("01.xlsm")
ws = wb.active
mastNumber = ws['H5']
But I don't want to edit a lot in gui.py (have just simple changes) so I planned to use a 2nd script (caller.py) which gets the data from Excel, then I import this to gui.py and there I just use the variable from caller.py. It works also as long as I start gui.py directly. When I start it from Excel I get an error msg.
error msg
So as long as the flow is not Excel -> gui-py -> which imports caller to get data from same Excel file -everything works fine.
I am open to any solution for this problem or a completely new approach if there is better for somebody with limited programming skills.
Looking at the error, can you try putting complete path of the excel file at line wb = openpyxl.load_workbook("01.xlsm") instead of just 01.xlsm.

Running .exe to execute batch file written in Python (Novice Programmer)

I wrote a Python script to prompt users for a spreadsheet to rename photos automatically based on the column called "Rename_Code" in the spreadsheet. The column is extracted and saved as a batch file, called "Rename_Code.bat". Below is the script:
import pandas as pd from tkinter.filedialog
import askopenfilename
#prompt user to browser excel sheet and load the spreadsheet in python
xl = pd.ExcelFile(askopenfilename() , index_col=None)
# load a sheet into a dataframe called df1
df1 = xl.parse('Sheet1')
# assign "Rename Code" column as rename_col
rename_col = df1['Rename_Code']
# extract the column and write as a batch file
rename_col.to_csv('Rename_Code.bat', index=False)
from subprocess import Popen p = Popen("Rename_Code.bat", cwd=r"C:\Users\username\Documents\XXXXX") stdout, stderr = p.communicate()
What I am trying to do is convert this script (in .pyw format) into an .exe so that anyone can use it without having Python installed in their computers. I used py2exe to convert the script. However, it kicked an error when I double clicked on the .exe. It says "Failed to launch application".
I am wondering if there is something wrong with the script. Alternatively, is there a better way to execute this, perhaps with Window Services? If yes, how? I am a novice programmer, most of my learning in programming involves a lot of trial and error and research.
I truly appreciate your feedback and help!

Python vba extract to get bin of macro

I am trying to add a vba_project to "Sheet1" of a workbook using python.
I am following XLSXWRITER documentation to get the bin of the VBA code from a different sheet which I would want to use in "Sheet1" of my new workbook.
I enter the below code in command prompt but I get the error: "'vba_extract.py' is not recognized as an internal or external command"
$ vba_extract.py Book1.xlsm
Extracted: vbaProject.bin
Can someone give me a step by step on how to extract the macro from old file as bin and then input into sheet1 of new workbook using python?
You have to tell the cmd you're running a python file.
Try this batch code:
cd C:\path\of\yourfile.py
python vba_extract.py Book1.xlsm
edit:
Added cd command, you have to be in the folder of the python file.
I figured this out today and just wanted to leave it here for any future people to use. This was so unbelievably frustrating to figure out how to do. If you are using the Pandas library, this is also relevant. Make sure to install xlsxwriter also.
1.Click on your windows start button and type 'cmd' and click on it to run the Command Prompt.
2.Once you have it open, you need to locate where the vba_extract.py file is. For me it was here:
C:\Users\yourusername\AppData\Local\Programs\Python\Python36-32\Scripts\vba_extract.py
3.Now, you need to get the path of the .xlsm file you want to take from. If you don't have a .xlsm file made. Make one. Here is an example:
C:\Users\yourusername\Desktop\excelfilename.xlsm
4.Now, back to the Command Prompt. This is exactly what you will type. You will take both items from steps 2 and 3 and combine then and hit enter. Here:
C:\Users\yourusername\AppData\Local\Programs\Python\Python36-32\Scripts\vba_extract.py C:\Users\yourusername\Desktop\excelfilename.xlsm
if it is successful, it will tell you this:
Extracted: vbaProject.bin
5.For this one I'm not sure. I assume that wherever your .xlsm file is where the .bin file will end up. For this example, it ended up on my desktop. It will have all the macros you created or had on the original .xlsm file.
C:\Users\yourusername\Desktop/vbaProject.bin
Here is an example of it being used in full code:
import pandas
import xlsxwriter
df_new = pd.read_csv('C:\\Users\\yourusername\\Desktop\\CSV1.csv')
writer = pd.ExcelWriter('C:\\Users\\yourusername\\Desktop\\CSV1.xlsx')
df_new.to_excel(writer, index = False, sheet_name = 'File Name', header = False)
pandaswb = writer.book
pandaswb.filename = 'C:\\Users\\yourusername\\Desktop\\newmacroexcelfile.xlsm')
pandaswb.add_vba_project(r'C:\Users\yourusername\Desktop/vbaProject.bin')
writer.save()

Categories

Resources