[{"data":1,"prerenderedAt":1376},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frun-an-excel-macro-from-python-with-win32com":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frun-an-excel-macro-from-python-with-win32com":1367},{"id":4,"title":5,"body":6,"dateModified":1339,"datePublished":1339,"description":1340,"extension":1341,"faq":1342,"meta":1351,"navigation":210,"path":1360,"seo":1361,"slug":1363,"stem":1364,"type":1365,"__hash__":1366},"docs\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frun-an-excel-macro-from-python-with-win32com\u002Findex.md","Run an Excel Macro from Python with win32com",{"type":7,"value":8,"toc":1324},"minimark",[9,24,125,130,154,157,161,168,175,178,182,402,405,409,510,513,521,532,542,548,552,571,642,661,665,668,788,791,795,798,801,804,808,819,825,893,900,904,1038,1042,1143,1150,1237,1240,1244,1252,1256,1263,1269,1275,1281,1285,1320],[10,11,12,13,17,18,23],"p",{},"A great deal of working Excel automation already exists as VBA, and rewriting it in Python is\nusually neither necessary nor wise. ",[14,15,16],"code",{},"Application.Run"," calls an existing macro from a Python script\nand hands back whatever it returns, which makes it possible to keep the macro as the unit of work\nand let Python handle scheduling, inputs and delivery around it. This guide, part of\n",[19,20,22],"a",{"href":21},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002F","Automating Excel with COM and pywin32",",\ncovers the call, its arguments, and the naming rules that account for most of its failures.",[25,26,34,35,34,39,34,43,34,50,34,57,34,67,34,73,34,78,34,85,34,90,34,94,34,97,34,100,34,104,34,107,34,112,34,117,34,120],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"0 0 760 232","img",[30,31],"mac-run-t","mac-run-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"How a macro call travels from Python into Excel",[40,41,42],"desc",{"id":31},"The script opens the workbook that contains the macro, calls Application.Run with a qualified name, and Excel executes the VBA in its own process and returns the function's value.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","232","#ffffff",[51,52,56],"text",{"x":53,"y":54,"style":55},"380.0","32","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","one call, one crossing",[44,58],{"x":59,"y":60,"width":61,"height":62,"rx":63,"fill":64,"stroke":65,"style":66},"24.0","74","208.0","96","12","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[51,68,72],{"x":69,"y":70,"style":71},"128.0","114","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","open the .xlsm",[51,74,77],{"x":69,"y":75,"style":76},"136","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","the macro must be loaded",[79,80],"line",{"x1":81,"y1":82,"x2":83,"y2":82,"stroke":84,"style":66},"237.0","122.0","269.0","var(--brand,#5b5cf0)",[86,87],"polygon",{"points":88,"fill":89},"269.0,122.0 260.0,117.0 260.0,127.0","#5b5cf0",[44,91],{"x":92,"y":60,"width":61,"height":62,"rx":63,"fill":93,"stroke":84,"style":66},"276.0","#f0f4ff",[51,95,16],{"x":53,"y":70,"style":96},"font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[51,98,99],{"x":53,"y":75,"style":76},"qualified name plus arguments",[79,101],{"x1":102,"y1":82,"x2":103,"y2":82,"stroke":84,"style":66},"489.0","521.0",[86,105],{"points":106,"fill":89},"521.0,122.0 512.0,117.0 512.0,127.0",[44,108],{"x":109,"y":60,"width":61,"height":62,"rx":63,"fill":110,"stroke":111,"style":66},"528.0","#d9f4f1","var(--teal,#0f9488)",[51,113,116],{"x":114,"y":70,"style":115},"632.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","VBA runs in Excel",[51,118,119],{"x":114,"y":75,"style":76},"returns a value to Python",[51,121,124],{"x":53,"y":122,"style":123},"210","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Python schedules and supplies inputs; VBA does the work in-process",[126,127,129],"h2",{"id":128},"prerequisites","Prerequisites",[131,132,137],"pre",{"className":133,"code":134,"language":135,"meta":136,"style":136},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pywin32\n","bash","",[14,138,139],{"__ignoreMap":136},[140,141,143,147,151],"span",{"class":79,"line":142},1,[140,144,146],{"class":145},"sMTad","pip",[140,148,150],{"class":149},"srMev"," install",[140,152,153],{"class":149}," pywin32\n",[10,155,156],{},"Windows with Excel installed, and a workbook containing the macro. If macro security is set to\ndisable everything, the file needs to be in a trusted location before any of this works.",[126,158,160],{"id":159},"create-a-macro-to-call","Create a macro to call",[10,162,163,164,167],{},"Save this as a module in ",[14,165,166],{},"automation.xlsm"," — Developer, Visual Basic, Insert Module — so the\nexamples below have something real to call.",[131,169,173],{"className":170,"code":172,"language":51,"meta":136},[171],"language-text","Public Function BuildSummary(quarter As String) As Long\n    Dim ws As Worksheet\n    Set ws = ThisWorkbook.Sheets(\"Summary\")\n    ws.Range(\"A1\").Value = \"Summary for \" & quarter\n    ws.Range(\"B2\").Value = Application.WorksheetFunction.Sum(ThisWorkbook.Sheets(\"Data\").Range(\"C:C\"))\n    BuildSummary = ThisWorkbook.Sheets(\"Data\").UsedRange.Rows.Count\nEnd Function\n",[14,174,172],{"__ignoreMap":136},[10,176,177],{},"It takes an argument, writes to the sheet and returns a number — enough to exercise every part of\nthe call.",[126,179,181],{"id":180},"run-it-from-python","Run it from Python",[131,183,187],{"className":184,"code":185,"language":186,"meta":136,"style":136},"language-python shiki shiki-themes github-light github-dark-high-contrast","import win32com.client as win32\n\nexcel = win32.DispatchEx(\"Excel.Application\")\nexcel.Visible = False\nexcel.DisplayAlerts = False\ntry:\n    book = excel.Workbooks.Open(r\"C:\\automation\\automation.xlsm\")\n    try:\n        rows = excel.Application.Run(\"automation.xlsm!BuildSummary\", \"2026-Q3\")\n        print(f\"macro processed {rows} rows\")\n        book.Save()\n    finally:\n        book.Close(SaveChanges=False)\nfinally:\n    excel.Quit()\n","python",[14,188,189,205,212,230,242,252,261,297,305,327,357,363,371,388,396],{"__ignoreMap":136},[140,190,191,195,199,202],{"class":79,"line":142},[140,192,194],{"class":193},"s-kum","import",[140,196,198],{"class":197},"skGVy"," win32com.client ",[140,200,201],{"class":193},"as",[140,203,204],{"class":197}," win32\n",[140,206,208],{"class":79,"line":207},2,[140,209,211],{"emptyLinePlaceholder":210},true,"\n",[140,213,215,218,221,224,227],{"class":79,"line":214},3,[140,216,217],{"class":197},"excel ",[140,219,220],{"class":193},"=",[140,222,223],{"class":197}," win32.DispatchEx(",[140,225,226],{"class":149},"\"Excel.Application\"",[140,228,229],{"class":197},")\n",[140,231,233,236,238],{"class":79,"line":232},4,[140,234,235],{"class":197},"excel.Visible ",[140,237,220],{"class":193},[140,239,241],{"class":240},"sP0c6"," False\n",[140,243,245,248,250],{"class":79,"line":244},5,[140,246,247],{"class":197},"excel.DisplayAlerts ",[140,249,220],{"class":193},[140,251,241],{"class":240},[140,253,255,258],{"class":79,"line":254},6,[140,256,257],{"class":193},"try",[140,259,260],{"class":197},":\n",[140,262,264,267,269,272,275,278,282,285,287,289,292,295],{"class":79,"line":263},7,[140,265,266],{"class":197},"    book ",[140,268,220],{"class":193},[140,270,271],{"class":197}," excel.Workbooks.Open(",[140,273,274],{"class":193},"r",[140,276,277],{"class":149},"\"C:",[140,279,281],{"class":280},"s_b0D","\\a",[140,283,284],{"class":149},"utomation",[140,286,281],{"class":280},[140,288,284],{"class":149},[140,290,291],{"class":240},".",[140,293,294],{"class":149},"xlsm\"",[140,296,229],{"class":197},[140,298,300,303],{"class":79,"line":299},8,[140,301,302],{"class":193},"    try",[140,304,260],{"class":197},[140,306,308,311,313,316,319,322,325],{"class":79,"line":307},9,[140,309,310],{"class":197},"        rows ",[140,312,220],{"class":193},[140,314,315],{"class":197}," excel.Application.Run(",[140,317,318],{"class":149},"\"automation.xlsm!BuildSummary\"",[140,320,321],{"class":197},", ",[140,323,324],{"class":149},"\"2026-Q3\"",[140,326,229],{"class":197},[140,328,330,333,336,339,342,346,349,352,355],{"class":79,"line":329},10,[140,331,332],{"class":240},"        print",[140,334,335],{"class":197},"(",[140,337,338],{"class":193},"f",[140,340,341],{"class":149},"\"macro processed ",[140,343,345],{"class":344},"sSjpA","{",[140,347,348],{"class":197},"rows",[140,350,351],{"class":344},"}",[140,353,354],{"class":149}," rows\"",[140,356,229],{"class":197},[140,358,360],{"class":79,"line":359},11,[140,361,362],{"class":197},"        book.Save()\n",[140,364,366,369],{"class":79,"line":365},12,[140,367,368],{"class":193},"    finally",[140,370,260],{"class":197},[140,372,374,377,381,383,386],{"class":79,"line":373},13,[140,375,376],{"class":197},"        book.Close(",[140,378,380],{"class":379},"sa561","SaveChanges",[140,382,220],{"class":193},[140,384,385],{"class":240},"False",[140,387,229],{"class":197},[140,389,391,394],{"class":79,"line":390},14,[140,392,393],{"class":193},"finally",[140,395,260],{"class":197},[140,397,399],{"class":79,"line":398},15,[140,400,401],{"class":197},"    excel.Quit()\n",[10,403,404],{},"The macro name is qualified with the file that holds it. Without that qualification Excel searches\nthe active workbook, which is whichever one happens to be in front — a source of failures that only\nappear when a second workbook is open.",[126,406,408],{"id":407},"naming-rules-that-decide-whether-the-call-works","Naming rules that decide whether the call works",[25,410,34,415,34,418,34,421,34,424,34,431,34,436,34,441,34,447,34,452,34,455,34,458,34,462,34,466,34,470,34,473,34,476,34,480,34,484,34,488,34,491,34,494,34,498,34,502,34,506],{"viewBox":411,"role":28,"ariaLabelledBy":412,"xmlns":32,"style":33},"0 0 760 308",[413,414],"mac-name-t","mac-name-d",[36,416,417],{"id":413},"The four naming rules behind most macro-not-found errors",[40,419,420],{"id":414},"Qualify with the file name, quote names containing spaces, use a sheet's code name for macros in sheet modules, and make the macro public and its workbook open.",[44,422],{"x":46,"y":46,"width":47,"height":423,"fill":49},"308",[44,425],{"x":426,"y":427,"width":428,"height":429,"rx":430,"fill":110,"stroke":111},"22","28","716","52","10",[432,433],"circle",{"cx":429,"cy":434,"r":435,"fill":89},"54.0","15",[51,437,440],{"x":429,"y":438,"style":439},"59.0","font-size:13px;font-weight:700;fill:#ffffff;text-anchor:middle","1",[51,442,446],{"x":443,"y":444,"style":445},"82","50","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","Qualify with the file name",[51,448,451],{"x":443,"y":449,"style":450},"68","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:start","\"automation.xlsm!BuildSummary\", not the path",[44,453],{"x":426,"y":454,"width":428,"height":429,"rx":430,"fill":110,"stroke":111},"90",[432,456],{"cx":429,"cy":457,"r":435,"fill":89},"116.0",[51,459,461],{"x":429,"y":460,"style":439},"121.0","2",[51,463,465],{"x":443,"y":464,"style":445},"112","Quote names with spaces",[51,467,469],{"x":443,"y":468,"style":450},"130","\"'monthly report.xlsm'!BuildSummary\"",[44,471],{"x":426,"y":472,"width":428,"height":429,"rx":430,"fill":110,"stroke":111},"152",[432,474],{"cx":429,"cy":475,"r":435,"fill":89},"178.0",[51,477,479],{"x":429,"y":478,"style":439},"183.0","3",[51,481,483],{"x":443,"y":482,"style":445},"174","Use the code name for sheet macros",[51,485,487],{"x":443,"y":486,"style":450},"192","Sheet1.Recalculate, not the tab label",[44,489],{"x":426,"y":490,"width":428,"height":429,"rx":430,"fill":110,"stroke":111},"214",[432,492],{"cx":429,"cy":493,"r":435,"fill":89},"240.0",[51,495,497],{"x":429,"y":496,"style":439},"245.0","4",[51,499,501],{"x":443,"y":500,"style":445},"236","Public, and already open",[51,503,505],{"x":443,"y":504,"style":450},"254","Run searches loaded workbooks only",[51,507,509],{"x":53,"y":508,"style":123},"296","four rules cover nearly every failure of this call",[10,511,512],{},"Four rules cover nearly every \"macro cannot be found\" error.",[10,514,515,516,518,519,291],{},"The workbook name goes before an exclamation mark, and it is the file name rather than a path:\n",[14,517,318],{},". If the name contains a space it must be wrapped in single quotes:\n",[14,520,469],{},[10,522,523,524,528,529,291],{},"A macro in a standard module can be called by its bare name. A macro inside a worksheet's code\nmodule needs the sheet's ",[525,526,527],"em",{},"code name"," — the one shown in the VBA project tree, not the tab label —\nas in ",[14,530,531],{},"\"automation.xlsm!Sheet1.Recalculate\"",[10,533,534,537,538,541],{},[14,535,536],{},"Private Sub"," is not callable from outside the project. Change it to ",[14,539,540],{},"Public",", or add a public\nwrapper that calls it.",[10,543,544,545,547],{},"And the workbook has to be open. ",[14,546,16],{}," does not open files; it searches what is loaded.",[126,549,551],{"id":550},"arguments-types-and-return-values","Arguments, types and return values",[10,553,554,555,558,559,562,563,566,567,570],{},"Arguments are positional and are converted automatically: Python strings become VBA ",[14,556,557],{},"String",",\nintegers become ",[14,560,561],{},"Long",", floats become ",[14,564,565],{},"Double",", and ",[14,568,569],{},"datetime"," objects become VBA dates. Up to\nthirty arguments are allowed, which is far more than any sane macro signature.",[131,572,574],{"className":184,"code":573,"language":186,"meta":136,"style":136},"result = excel.Application.Run(\n    \"automation.xlsm!BuildRegional\",\n    \"North\",                       # String\n    2026,                          # Long\n    0.175,                         # Double\n    True,                          # Boolean\n)\n",[14,575,576,586,594,606,617,628,638],{"__ignoreMap":136},[140,577,578,581,583],{"class":79,"line":142},[140,579,580],{"class":197},"result ",[140,582,220],{"class":193},[140,584,585],{"class":197}," excel.Application.Run(\n",[140,587,588,591],{"class":79,"line":207},[140,589,590],{"class":149},"    \"automation.xlsm!BuildRegional\"",[140,592,593],{"class":197},",\n",[140,595,596,599,602],{"class":79,"line":214},[140,597,598],{"class":149},"    \"North\"",[140,600,601],{"class":197},",                       ",[140,603,605],{"class":604},"s-wDw","# String\n",[140,607,608,611,614],{"class":79,"line":232},[140,609,610],{"class":240},"    2026",[140,612,613],{"class":197},",                          ",[140,615,616],{"class":604},"# Long\n",[140,618,619,622,625],{"class":79,"line":244},[140,620,621],{"class":240},"    0.175",[140,623,624],{"class":197},",                         ",[140,626,627],{"class":604},"# Double\n",[140,629,630,633,635],{"class":79,"line":254},[140,631,632],{"class":240},"    True",[140,634,613],{"class":197},[140,636,637],{"class":604},"# Boolean\n",[140,639,640],{"class":79,"line":263},[140,641,229],{"class":197},[10,643,644,645,648,649,652,653,656,657,291],{},"A VBA ",[14,646,647],{},"Function"," returns its value; a ",[14,650,651],{},"Sub"," returns ",[14,654,655],{},"None",". If a macro needs to hand back several\nvalues, the workable options are to return a delimited string, or — usually better — to have the\nmacro write its results into a scratch range and read that range from Python afterwards, using the\nblock reads described in\n",[19,658,660],{"href":659},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fread-and-write-cell-ranges-with-win32com\u002F","Read and Write Cell Ranges with win32com",[126,662,664],{"id":663},"passing-a-lot-of-data-into-a-macro","Passing a lot of data into a macro",[10,666,667],{},"Do not pass a large array as an argument. The conversion is slow and the argument limit is low.\nWrite the data into a staging sheet in one range assignment, then call the macro with the address:",[131,669,671],{"className":184,"code":670,"language":186,"meta":136,"style":136},"staging = book.Sheets(\"Staging\")\nstaging.Range(\"A1:C1\").Value = [[\"Region\", \"Rep\", \"Revenue\"]]\nstaging.Range(f\"A2:C{len(rows) + 1}\").Value = rows        # one crossing\nexcel.Application.Run(\"automation.xlsm!ProcessStaging\", f\"A2:C{len(rows) + 1}\")\n",[14,672,673,688,720,758],{"__ignoreMap":136},[140,674,675,678,680,683,686],{"class":79,"line":142},[140,676,677],{"class":197},"staging ",[140,679,220],{"class":193},[140,681,682],{"class":197}," book.Sheets(",[140,684,685],{"class":149},"\"Staging\"",[140,687,229],{"class":197},[140,689,690,693,696,699,701,704,707,709,712,714,717],{"class":79,"line":207},[140,691,692],{"class":197},"staging.Range(",[140,694,695],{"class":149},"\"A1:C1\"",[140,697,698],{"class":197},").Value ",[140,700,220],{"class":193},[140,702,703],{"class":197}," [[",[140,705,706],{"class":149},"\"Region\"",[140,708,321],{"class":197},[140,710,711],{"class":149},"\"Rep\"",[140,713,321],{"class":197},[140,715,716],{"class":149},"\"Revenue\"",[140,718,719],{"class":197},"]]\n",[140,721,722,724,726,729,731,734,737,740,743,745,748,750,752,755],{"class":79,"line":214},[140,723,692],{"class":197},[140,725,338],{"class":193},[140,727,728],{"class":149},"\"A2:C",[140,730,345],{"class":344},[140,732,733],{"class":240},"len",[140,735,736],{"class":197},"(rows) ",[140,738,739],{"class":193},"+",[140,741,742],{"class":240}," 1",[140,744,351],{"class":344},[140,746,747],{"class":149},"\"",[140,749,698],{"class":197},[140,751,220],{"class":193},[140,753,754],{"class":197}," rows        ",[140,756,757],{"class":604},"# one crossing\n",[140,759,760,763,766,768,770,772,774,776,778,780,782,784,786],{"class":79,"line":232},[140,761,762],{"class":197},"excel.Application.Run(",[140,764,765],{"class":149},"\"automation.xlsm!ProcessStaging\"",[140,767,321],{"class":197},[140,769,338],{"class":193},[140,771,728],{"class":149},[140,773,345],{"class":344},[140,775,733],{"class":240},[140,777,736],{"class":197},[140,779,739],{"class":193},[140,781,742],{"class":240},[140,783,351],{"class":344},[140,785,747],{"class":149},[140,787,229],{"class":197},[10,789,790],{},"This keeps the expensive part — moving values across the process boundary — to a single call, and\nleaves the macro doing what it is good at.",[126,792,794],{"id":793},"deciding-what-stays-in-vba","Deciding what stays in VBA",[10,796,797],{},"A script that can call macros invites a question that is worth answering deliberately: which half of\nthe logic belongs where. The useful test is whether the work depends on Excel. Anything that reads a\ndatabase, formats an email, decides which files to process or writes a log belongs in Python, where\nit can be tested, version-controlled and run on a schedule. Anything that manipulates the workbook\nin bulk — filling a thousand rows, applying a template's formatting, rebuilding a chart — is faster\ninside VBA, because it happens in the same process as the data.",[10,799,800],{},"That division also survives change better than either extreme. A macro that has grown to include\ndatabase credentials and file paths is a macro that nobody can move; a Python script that drives\nExcel cell by cell is one that nobody can speed up. Keeping the boundary at \"Python decides, VBA\nmanipulates\" leaves each side doing what it is good at, and makes the eventual migration away from\nVBA a matter of replacing one call rather than untangling a program.",[10,802,803],{},"There is a practical corollary. Macros called from Python should take arguments rather than reading\ntheir own configuration. A macro that opens a hard-coded path is one that can only ever do one job;\nthe same macro taking a range address and a multiplier can be called for any month, any region and\nany file the script chooses.",[126,805,807],{"id":806},"handling-a-macro-that-fails","Handling a macro that fails",[10,809,810,811,814,815,818],{},"VBA errors do not arrive as Python exceptions in a useful form. An unhandled error inside a macro\nraises a dialog, and with ",[14,812,813],{},"DisplayAlerts"," suppressed the call typically comes back as a generic COM\nerror naming the ",[14,816,817],{},"Run"," method rather than the problem. The workable pattern is to have the macro\ncatch its own errors and report them as a return value.",[131,820,823],{"className":821,"code":822,"language":51,"meta":136},[171],"Public Function BuildSummary(quarter As String) As String\n    On Error GoTo Failed\n    ' ... work ...\n    BuildSummary = \"OK\"\n    Exit Function\nFailed:\n    BuildSummary = \"ERROR: \" & Err.Number & \" \" & Err.Description\nEnd Function\n",[14,824,822],{"__ignoreMap":136},[131,826,828],{"className":184,"code":827,"language":186,"meta":136,"style":136},"outcome = excel.Application.Run(\"automation.xlsm!BuildSummary\", \"2026-Q3\")\nif not str(outcome).startswith(\"OK\"):\n    raise RuntimeError(f\"macro failed: {outcome}\")\n",[14,829,830,847,867],{"__ignoreMap":136},[140,831,832,835,837,839,841,843,845],{"class":79,"line":142},[140,833,834],{"class":197},"outcome ",[140,836,220],{"class":193},[140,838,315],{"class":197},[140,840,318],{"class":149},[140,842,321],{"class":197},[140,844,324],{"class":149},[140,846,229],{"class":197},[140,848,849,852,855,858,861,864],{"class":79,"line":207},[140,850,851],{"class":193},"if",[140,853,854],{"class":193}," not",[140,856,857],{"class":240}," str",[140,859,860],{"class":197},"(outcome).startswith(",[140,862,863],{"class":149},"\"OK\"",[140,865,866],{"class":197},"):\n",[140,868,869,872,875,877,879,882,884,887,889,891],{"class":79,"line":214},[140,870,871],{"class":193},"    raise",[140,873,874],{"class":240}," RuntimeError",[140,876,335],{"class":197},[140,878,338],{"class":193},[140,880,881],{"class":149},"\"macro failed: ",[140,883,345],{"class":344},[140,885,886],{"class":197},"outcome",[140,888,351],{"class":344},[140,890,747],{"class":149},[140,892,229],{"class":197},[10,894,895,896,291],{},"Twelve extra lines turn an opaque hang into a message that names the failing line's error number,\nwhich is the difference between fixing it in five minutes and reproducing it by hand on a Windows\nmachine. The same reasoning applies to the retry and logging patterns in\n",[19,897,899],{"href":898},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fretry-a-failed-excel-report-job-in-python\u002F","Retry a Failed Excel Report Job in Python",[126,901,903],{"id":902},"common-pitfalls","Common pitfalls",[905,906,907,923],"table",{},[908,909,910],"thead",{},[911,912,913,917,920],"tr",{},[914,915,916],"th",{},"Symptom",[914,918,919],{},"Cause",[914,921,922],{},"Fix",[924,925,926,943,954,971,993,1012],"tbody",{},[911,927,928,934,937],{},[929,930,931],"td",{},[14,932,933],{},"Cannot run the macro ... may not be available",[929,935,936],{},"The workbook is not open, or the name is unqualified",[929,938,939,940],{},"Open the file first and qualify with ",[14,941,942],{},"\"file.xlsm!Macro\"",[911,944,945,948,951],{},[929,946,947],{},"Works by hand, fails from Python",[929,949,950],{},"Macros disabled by Trust Center for this account",[929,952,953],{},"Put the file in a trusted location",[911,955,956,962,965],{},[929,957,958,959,961],{},"A ",[14,960,536],{}," is not found",[929,963,964],{},"Private members are not exposed to callers",[929,966,967,968,970],{},"Make it ",[14,969,540],{},", or add a public wrapper",[911,972,973,979,987],{},[929,974,975,976,978],{},"The call returns ",[14,977,655],{}," unexpectedly",[929,980,981,982,984,985],{},"It is a ",[14,983,651],{},", not a ",[14,986,647],{},[929,988,989,990,992],{},"Change to ",[14,991,647],{},", or read the result from a cell",[911,994,995,998,1001],{},[929,996,997],{},"The script hangs with no output",[929,999,1000],{},"The macro raised and VBA is showing an error dialog",[929,1002,1003,1004,1007,1008,1011],{},"Set ",[14,1005,1006],{},"DisplayAlerts = False"," and add ",[14,1009,1010],{},"On Error"," handling in the macro",[911,1013,1014,1024,1027],{},[929,1015,1016,1019,1020,1023],{},[14,1017,1018],{},"xlsm"," saved as ",[14,1021,1022],{},"xlsx"," afterwards, macros gone",[929,1025,1026],{},"The extension decides whether VBA survives",[929,1028,1029,1030,1033,1034],{},"Save as ",[14,1031,1032],{},".xlsm",", and see ",[19,1035,1037],{"href":1036},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fwork-with-macro-enabled-xlsm-files-in-openpyxl\u002F","macro-enabled files in openpyxl",[126,1039,1041],{"id":1040},"performance-and-scale","Performance and scale",[25,1043,34,1048,34,1051,34,1054,34,1057,34,1065,34,1071,34,1077,34,1082,34,1086,34,1090,34,1093,34,1098,34,1102,34,1105,34,1108,34,1111,34,1119,34,1124,34,1129,34,1132,34,1136,34,1139],{"viewBox":1044,"role":28,"ariaLabelledBy":1045,"xmlns":32,"style":33},"0 0 760 201",[1046,1047],"mac-loop-t","mac-loop-d",[36,1049,1050],{"id":1046},"Where the loop should live",[40,1052,1053],{"id":1047},"Driving twenty thousand cells from Python means twenty thousand process crossings, while passing a range address to the macro lets VBA do the same loop inside Excel with one crossing.",[44,1055],{"x":46,"y":46,"width":47,"height":1056,"fill":49},"201",[44,1058],{"x":1059,"y":427,"width":1060,"height":1061,"rx":1062,"fill":1063,"stroke":1064,"style":66},"20","270.0","139","14","#fee8f2","var(--accent,#d81b73)",[51,1066,1070],{"x":1067,"y":1068,"style":1069},"155.0","54","font-size:13px;font-weight:700;fill:var(--accent,#d81b73);text-anchor:middle","loop in Python",[79,1072],{"x1":1073,"y1":1074,"x2":1075,"y2":1074,"stroke":1064,"style":1076},"36","64","274.0","stroke-width:1px",[51,1078,1081],{"x":1067,"y":1079,"style":1080},"86","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","20,000 COM calls",[51,1083,1085],{"x":1067,"y":1084,"style":1080},"109","minutes of runtime",[51,1087,1089],{"x":1067,"y":1088,"style":1080},"132","each cell crosses",[44,1091],{"x":1092,"y":427,"width":1060,"height":1061,"rx":1062,"fill":110,"stroke":111,"style":66},"470.0",[51,1094,1097],{"x":1095,"y":1068,"style":1096},"605.0","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","loop in VBA",[79,1099],{"x1":1100,"y1":1074,"x2":1101,"y2":1074,"stroke":111,"style":1076},"486.0","724.0",[51,1103,1104],{"x":1095,"y":1079,"style":1080},"1 COM call",[51,1106,1107],{"x":1095,"y":1084,"style":1080},"seconds of runtime",[51,1109,1110],{"x":1095,"y":1088,"style":1080},"no boundary inside",[44,1112],{"x":1113,"y":1114,"width":1115,"height":1116,"rx":1117,"fill":1118,"stroke":84},"316.0","78.5","128","38","19","#ebebfd",[51,1120,1123],{"x":53,"y":1121,"style":1122},"102.5","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","pass a range",[79,1125],{"x1":1126,"y1":1127,"x2":1128,"y2":1127,"stroke":84,"style":66},"295.0","97.5","309.0",[86,1130],{"points":1131,"fill":89},"309.0,97.5 300.0,92.5 300.0,102.5",[79,1133],{"x1":1134,"y1":1127,"x2":1135,"y2":1127,"stroke":84,"style":66},"449.0","463.0",[86,1137],{"points":1138,"fill":89},"463.0,97.5 454.0,92.5 454.0,102.5",[51,1140,1142],{"x":53,"y":1141,"style":123},"187","give the macro an address, not a value at a time",[10,1144,1145,1146,1149],{},"The macro itself runs at VBA speed, which is usually fine — VBA inside Excel is far faster than\nCOM calls from outside it, because there is no process boundary to cross. That produces a useful\nrule: work that touches many cells belongs ",[525,1147,1148],{},"inside"," the macro, and Python should hand it a range\naddress rather than iterating cells itself.",[131,1151,1153],{"className":184,"code":1152,"language":186,"meta":136,"style":136},"# Slow: Python drives every cell through COM.\nfor index in range(2, 20002):\n    sheet.Cells(index, 4).Value = sheet.Cells(index, 2).Value * 1.2\n\n# Fast: one call, and VBA does the loop in-process.\nexcel.Application.Run(\"automation.xlsm!ApplyUplift\", \"B2:B20001\", 1.2)\n",[14,1154,1155,1160,1185,1209,1213,1218],{"__ignoreMap":136},[140,1156,1157],{"class":79,"line":142},[140,1158,1159],{"class":604},"# Slow: Python drives every cell through COM.\n",[140,1161,1162,1165,1168,1171,1174,1176,1178,1180,1183],{"class":79,"line":207},[140,1163,1164],{"class":193},"for",[140,1166,1167],{"class":197}," index ",[140,1169,1170],{"class":193},"in",[140,1172,1173],{"class":240}," range",[140,1175,335],{"class":197},[140,1177,461],{"class":240},[140,1179,321],{"class":197},[140,1181,1182],{"class":240},"20002",[140,1184,866],{"class":197},[140,1186,1187,1190,1192,1194,1196,1199,1201,1203,1206],{"class":79,"line":214},[140,1188,1189],{"class":197},"    sheet.Cells(index, ",[140,1191,497],{"class":240},[140,1193,698],{"class":197},[140,1195,220],{"class":193},[140,1197,1198],{"class":197}," sheet.Cells(index, ",[140,1200,461],{"class":240},[140,1202,698],{"class":197},[140,1204,1205],{"class":193},"*",[140,1207,1208],{"class":240}," 1.2\n",[140,1210,1211],{"class":79,"line":232},[140,1212,211],{"emptyLinePlaceholder":210},[140,1214,1215],{"class":79,"line":244},[140,1216,1217],{"class":604},"# Fast: one call, and VBA does the loop in-process.\n",[140,1219,1220,1222,1225,1227,1230,1232,1235],{"class":79,"line":254},[140,1221,762],{"class":197},[140,1223,1224],{"class":149},"\"automation.xlsm!ApplyUplift\"",[140,1226,321],{"class":197},[140,1228,1229],{"class":149},"\"B2:B20001\"",[140,1231,321],{"class":197},[140,1233,1234],{"class":240},"1.2",[140,1236,229],{"class":197},[10,1238,1239],{},"Two further settings help when a macro rewrites a lot of the sheet: turning off screen updating and\nswitching calculation to manual for the duration. Both are set from Python before the call and\nrestored afterwards, and together they routinely halve the runtime of a heavy macro.",[126,1241,1243],{"id":1242},"conclusion","Conclusion",[10,1245,1246,1248,1249,1251],{},[14,1247,16],{}," lets an existing VBA investment keep earning while Python takes over the parts it\nis better at — inputs, scheduling, delivery and logging. Qualify the macro name with its workbook,\nmake sure that workbook is open, keep the macro ",[14,1250,540],{},", and push bulk work into VBA rather than\nlooping over cells from outside. When the macro needs a lot of data, stage it in a range with a\nsingle assignment and pass the address.",[126,1253,1255],{"id":1254},"frequently-asked-questions","Frequently asked questions",[10,1257,1258,1262],{},[1259,1260,1261],"strong",{},"Why does Application.Run say the macro cannot be found?","\nAlmost always a naming problem. Qualify the macro with the workbook that contains it — \"book.xlsm!ModuleName.MacroName\" — and remember that a macro inside a sheet's code module needs the sheet's code name, not its tab name. A macro declared Private is not callable from outside at all.",[10,1264,1265,1268],{},[1259,1266,1267],{},"Can I pass arguments and get a return value?","\nYes. Positional arguments follow the macro name in Application.Run, and a VBA Function returns its value straight back to Python. A Sub returns None because it has nothing to return.",[10,1270,1271,1274],{},[1259,1272,1273],{},"Does the macro have to live in the workbook I opened?","\nNo. It can live in another open workbook, in an add-in, or in PERSONAL.XLSB. Qualify the name with whichever file holds it, and make sure that file is open before the call.",[10,1276,1277,1280],{},[1259,1278,1279],{},"Why does the call fail with macros disabled?","\nTrust Center settings block VBA in files from untrusted locations. Put the workbook in a trusted location, or enable macros for the account that runs the script — there is no COM-side override.",[126,1282,1284],{"id":1283},"related","Related",[1286,1287,1288,1295,1302,1307,1314],"ul",{},[1289,1290,1291,1292,1294],"li",{},"Up one level: ",[19,1293,22],{"href":21}," — the session skeleton these calls sit inside.",[1289,1296,1297,1301],{},[19,1298,1300],{"href":1299},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-xlwings-basics\u002Fxlwings-run-macro-from-python-example\u002F","xlwings Run Macro from Python Example"," — the same job through the friendlier wrapper.",[1289,1303,1304,1306],{},[19,1305,660],{"href":659}," — staging data for a macro in one call.",[1289,1308,1309,1313],{},[19,1310,1312],{"href":1311},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fhandle-com-errors-and-excel-dialog-prompts-in-python\u002F","Handle COM Errors and Excel Dialog Prompts in Python"," — what to do when a macro raises inside Excel.",[1289,1315,1316,1319],{},[19,1317,1318],{"href":1036},"Work with Macro-Enabled .xlsm Files in openpyxl"," — editing the same files without running them.",[1321,1322,1323],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":136,"searchDepth":207,"depth":207,"links":1325},[1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338],{"id":128,"depth":207,"text":129},{"id":159,"depth":207,"text":160},{"id":180,"depth":207,"text":181},{"id":407,"depth":207,"text":408},{"id":550,"depth":207,"text":551},{"id":663,"depth":207,"text":664},{"id":793,"depth":207,"text":794},{"id":806,"depth":207,"text":807},{"id":902,"depth":207,"text":903},{"id":1040,"depth":207,"text":1041},{"id":1242,"depth":207,"text":1243},{"id":1254,"depth":207,"text":1255},{"id":1283,"depth":207,"text":1284},"2026-09-04","Call existing VBA from Python with Application.Run: qualified macro names, positional arguments, return values, and staging bulk data so the loop runs inside Excel.","md",[1343,1345,1347,1349],{"q":1261,"a":1344},"Almost always a naming problem. Qualify the macro with the workbook that contains it — \"book.xlsm!ModuleName.MacroName\" — and remember that a macro inside a sheet's code module needs the sheet's code name, not its tab name. A macro declared Private is not callable from outside at all.",{"q":1267,"a":1346},"Yes. Positional arguments follow the macro name in Application.Run, and a VBA Function returns its value straight back to Python. A Sub returns None because it has nothing to return.",{"q":1273,"a":1348},"No. It can live in another open workbook, in an add-in, or in PERSONAL.XLSB. Qualify the name with whichever file holds it, and make sure that file is open before the call.",{"q":1279,"a":1350},"Trust Center settings block VBA in files from untrusted locations. Put the workbook in a trusted location, or enable macros for the account that runs the script — there is no COM-side override.",{"breadcrumb":1352},[1353,1356,1359],{"name":1354,"item":1355},"Home","\u002F",{"name":1357,"item":1358},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":22,"item":21},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frun-an-excel-macro-from-python-with-win32com",{"title":5,"description":1362},"Use Application.Run to call VBA macros from Python — naming rules, arguments and return values, Trust Center gotchas, and how to hand a macro bulk data efficiently.","run-an-excel-macro-from-python-with-win32com","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frun-an-excel-macro-from-python-with-win32com\u002Findex","how-to","cVAedsJh897Yo1ryKW2XG2-CwkwNOGWhxqqTw3Q1Jrg",[1368,1372],{"title":1369,"path":1370,"stem":1371,"children":-1},"Refresh Excel Data Connections and Recalculate with win32com","\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frefresh-excel-data-connections-and-recalculate-with-win32com","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frefresh-excel-data-connections-and-recalculate-with-win32com\u002Findex",{"title":1373,"path":1374,"stem":1375,"children":-1},"xlwings vs pywin32 for Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fxlwings-vs-pywin32-for-excel-automation","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fxlwings-vs-pywin32-for-excel-automation\u002Findex",1788710154301]