[{"data":1,"prerenderedAt":2505},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fadd-a-vba-macro-to-a-workbook-with-xlsxwriter":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fadd-a-vba-macro-to-a-workbook-with-xlsxwriter":2476,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fadd-a-vba-macro-to-a-workbook-with-xlsxwriter":2484,"children:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fadd-a-vba-macro-to-a-workbook-with-xlsxwriter":2488,"related:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fadd-a-vba-macro-to-a-workbook-with-xlsxwriter":2489},{"id":4,"title":5,"body":6,"dateModified":2443,"datePublished":2443,"description":2444,"extension":2445,"faq":2446,"keyword":2455,"kind":2456,"meta":2457,"navigation":337,"path":2466,"seo":2467,"slug":2470,"stem":2471,"topics":2472,"type":2474,"__hash__":2475},"docs\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fadd-a-vba-macro-to-a-workbook-with-xlsxwriter\u002Findex.md","Add a VBA Macro to a Workbook with xlsxwriter",{"type":7,"value":8,"toc":2429},"minimark",[9,24,29,60,77,257,261,275,309,316,446,456,460,469,632,635,696,708,807,811,818,943,962,1205,1209,1240,1417,1427,1490,1497,1501,1504,1903,1909,1913,1920,2083,2090,2240,2244,2257,2264,2268,2330,2334,2363,2367,2373,2379,2385,2391,2395,2425],[10,11,12,13,17,18,23],"p",{},"Some reports still need a macro: a button that refreshes a pivot, a ",[14,15,16],"code",{},"Workbook_Open"," handler that selects the right sheet, a small routine the finance team runs every month. When the report itself is generated by Python, the macro has to travel inside the file Python writes. xlsxwriter cannot compile VBA, but it can embed a VBA project that Excel compiled, which is enough: write the macros once in Excel, extract the binary, and attach it to every workbook Python generates. This page is part of ",[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",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install xlsxwriter oletools openpyxl\n","bash","",[14,37,38],{"__ignoreMap":35},[39,40,43,47,51,54,57],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sMTad","pip",[39,48,50],{"class":49},"srMev"," install",[39,52,53],{"class":49}," xlsxwriter",[39,55,56],{"class":49}," oletools",[39,58,59],{"class":49}," openpyxl\n",[10,61,62,65,66,69,70,73,74,76],{},[14,63,64],{},"template\u002Fmacros_template.xlsm"," stands in for a workbook someone saved in Excel after writing the macros in the VBA editor (Alt+F11). Its project holds one standard module, ",[14,67,68],{},"Module1",", with a procedure named ",[14,71,72],{},"say_hello"," that shows a message box. The same steps work for any project: a dozen modules, class modules, a ",[14,75,16],{}," handler.",[78,79,87,88,87,92,87,96,87,104,87,114,87,125,87,132,87,136,87,141,87,147,87,152,87,155,87,161,87,165,87,168,87,171,87,175,87,178,87,182,87,189,87,194,87,198,87,201,87,205,87,208,87,211,87,217,87,221,87,224,87,227,87,231,87,234,87,239,87,246,87,251,87,254],"svg",{"viewBox":80,"role":81,"ariaLabelledBy":82,"xmlns":85,"style":86},"4 0 752 142","img",[83,84],"xv-flow-t","xv-flow-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[89,90,91],"title",{"id":83},"Macros written once, attached to every report",[93,94,95],"desc",{"id":84},"Macros are written in Excel and saved in a template .xlsm. vba_extract copies the vbaProject.bin part out once and it is committed with the code. Each run of the Python report writes a new workbook with xlsxwriter, attaches the binary, and saves it as an .xlsm file.",[97,98],"rect",{"x":99,"y":100,"width":101,"height":102,"fill":103},"4","0","752","142","#ffffff",[97,105],{"x":106,"y":107,"width":108,"height":109,"rx":110,"fill":111,"stroke":112,"style":113},"20.0","16.0","115.2","110.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[115,116,118],"g",{"transform":117},"translate(65.6,29.0) scale(1.000)",[119,120],"path",{"d":121,"fill":122,"stroke":123,"style":124},"M9 8l-4 4 4 4 M15 8l4 4-4 4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[126,127,131],"text",{"x":128,"y":129,"style":130},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Write in",[126,133,135],{"x":128,"y":134,"style":130},"93.0","Excel",[126,137,140],{"x":128,"y":138,"style":139},"112.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","VBA editor",[41,142],{"x1":143,"y1":144,"x2":145,"y2":144,"stroke":146,"style":113},"140.2","71.2","162.2","var(--brand,#5b5cf0)",[148,149],"polygon",{"points":150,"fill":151},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[97,153],{"x":154,"y":107,"width":108,"height":109,"rx":110,"fill":111,"stroke":112,"style":113},"171.2",[115,156,158],{"transform":157},"translate(216.8,29.0) scale(1.000)",[119,159],{"d":160,"fill":122,"stroke":123,"style":124},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6",[126,162,164],{"x":163,"y":129,"style":130},"228.8","Template",[126,166,167],{"x":163,"y":134,"style":130},".xlsm",[126,169,170],{"x":163,"y":138,"style":139},"saved once",[41,172],{"x1":173,"y1":144,"x2":174,"y2":144,"stroke":146,"style":113},"291.4","313.4",[148,176],{"points":177,"fill":151},"317.4,71.2 308.4,76.2 308.4,66.2",[97,179],{"x":180,"y":107,"width":108,"height":109,"rx":110,"fill":181,"stroke":146,"style":113},"322.4","#f0f4ff",[115,183,185],{"transform":184},"translate(368.0,29.0) scale(1.000)",[119,186],{"d":187,"fill":122,"stroke":188,"style":124},"M12 2.5l8.5 4.7v9.6L12 21.5l-8.5-4.7V7.2z M3.5 7.2l8.5 4.7 8.5-4.7 M12 11.9v9.6","var(--brand-strong,#4338ca)",[126,190,193],{"x":191,"y":129,"style":192},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","vbaProject.bin",[126,195,197],{"x":191,"y":196,"style":139},"95.0","extracted and",[126,199,200],{"x":191,"y":138,"style":139},"committed",[41,202],{"x1":203,"y1":144,"x2":204,"y2":144,"stroke":146,"style":113},"442.6","464.6",[148,206],{"points":207,"fill":151},"468.6,71.2 459.6,76.2 459.6,66.2",[97,209],{"x":210,"y":107,"width":108,"height":109,"rx":110,"fill":181,"stroke":146,"style":113},"473.6",[115,212,214],{"transform":213},"translate(519.2,29.0) scale(1.000)",[119,215],{"d":216,"fill":122,"stroke":188,"style":124},"M12 9a3 3 0 1 0 .01 0z M12 2.5v3 M12 18.5v3 M2.5 12h3 M18.5 12h3 M5.3 5.3l2.1 2.1 M16.6 16.6l2.1 2.1 M5.3 18.7l2.1-2.1 M16.6 7.4l2.1-2.1",[126,218,220],{"x":219,"y":129,"style":192},"531.2","xlsxwriter",[126,222,223],{"x":219,"y":134,"style":192},"run",[126,225,226],{"x":219,"y":138,"style":139},"add_vba_project",[41,228],{"x1":229,"y1":144,"x2":230,"y2":144,"stroke":146,"style":113},"593.8","615.8",[148,232],{"points":233,"fill":151},"619.8,71.2 610.8,76.2 610.8,66.2",[97,235],{"x":236,"y":107,"width":108,"height":109,"rx":110,"fill":237,"stroke":238,"style":113},"624.8","#fdefd8","var(--gold,#b4740a)",[115,240,242],{"transform":241},"translate(670.4,29.0) scale(1.000)",[119,243],{"d":244,"fill":122,"stroke":245,"style":124},"M6 3h9l4 4v14H6z M15 3v4h4","var(--gold-ink,#7a4e06)",[126,247,250],{"x":248,"y":129,"style":249},"682.4","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Report .xlsm",[126,252,253],{"x":248,"y":196,"style":139},"data plus",[126,255,256],{"x":248,"y":138,"style":139},"macros",[25,258,260],{"id":259},"step-1-extract-vbaprojectbin-from-a-macro-workbook","Step 1 — Extract vbaProject.bin from a macro workbook",[10,262,263,264,266,267,270,271,274],{},"An ",[14,265,167],{}," file is a zip archive, and the compiled macros live in one part of it, ",[14,268,269],{},"xl\u002FvbaProject.bin",". xlsxwriter installs a small command-line script, ",[14,272,273],{},"vba_extract.py",", that copies that part into the current folder:",[30,276,278],{"className":32,"code":277,"language":34,"meta":35,"style":35},"cd template && vba_extract.py macros_template.xlsm && ls -l vbaProject.bin\n",[14,279,280],{"__ignoreMap":35},[39,281,282,286,289,293,295,298,300,303,306],{"class":41,"line":42},[39,283,285],{"class":284},"sP0c6","cd",[39,287,288],{"class":49}," template",[39,290,292],{"class":291},"skGVy"," && ",[39,294,273],{"class":45},[39,296,297],{"class":49}," macros_template.xlsm",[39,299,292],{"class":291},[39,301,302],{"class":45},"ls",[39,304,305],{"class":284}," -l",[39,307,308],{"class":49}," vbaProject.bin\n",[10,310,311,312,315],{},"If the project is digitally signed, the script also writes ",[14,313,314],{},"vbaProjectSignature.bin",". The same thing takes three lines of Python, which is handy in a build script:",[30,317,321],{"className":318,"code":319,"language":320,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import zipfile\n\nwith zipfile.ZipFile(\"template\u002Fmacros_template.xlsm\") as z:\n    data = z.read(\"xl\u002FvbaProject.bin\")\nwith open(\"vbaProject.bin\", \"wb\") as f:\n    f.write(data)\nprint(f\"{len(data):,} bytes\")\n","python",[14,322,323,332,339,360,378,405,411],{"__ignoreMap":35},[39,324,325,329],{"class":41,"line":42},[39,326,328],{"class":327},"s-kum","import",[39,330,331],{"class":291}," zipfile\n",[39,333,335],{"class":41,"line":334},2,[39,336,338],{"emptyLinePlaceholder":337},true,"\n",[39,340,342,345,348,351,354,357],{"class":41,"line":341},3,[39,343,344],{"class":327},"with",[39,346,347],{"class":291}," zipfile.ZipFile(",[39,349,350],{"class":49},"\"template\u002Fmacros_template.xlsm\"",[39,352,353],{"class":291},") ",[39,355,356],{"class":327},"as",[39,358,359],{"class":291}," z:\n",[39,361,363,366,369,372,375],{"class":41,"line":362},4,[39,364,365],{"class":291},"    data ",[39,367,368],{"class":327},"=",[39,370,371],{"class":291}," z.read(",[39,373,374],{"class":49},"\"xl\u002FvbaProject.bin\"",[39,376,377],{"class":291},")\n",[39,379,381,383,386,389,392,395,398,400,402],{"class":41,"line":380},5,[39,382,344],{"class":327},[39,384,385],{"class":284}," open",[39,387,388],{"class":291},"(",[39,390,391],{"class":49},"\"vbaProject.bin\"",[39,393,394],{"class":291},", ",[39,396,397],{"class":49},"\"wb\"",[39,399,353],{"class":291},[39,401,356],{"class":327},[39,403,404],{"class":291}," f:\n",[39,406,408],{"class":41,"line":407},6,[39,409,410],{"class":291},"    f.write(data)\n",[39,412,414,417,419,422,425,429,432,435,438,441,444],{"class":41,"line":413},7,[39,415,416],{"class":284},"print",[39,418,388],{"class":291},[39,420,421],{"class":327},"f",[39,423,424],{"class":49},"\"",[39,426,428],{"class":427},"sSjpA","{",[39,430,431],{"class":284},"len",[39,433,434],{"class":291},"(data)",[39,436,437],{"class":327},":,",[39,439,440],{"class":427},"}",[39,442,443],{"class":49}," bytes\"",[39,445,377],{"class":291},[10,447,448,449,451,452,23],{},"Commit ",[14,450,193],{}," next to the report code. When a macro changes, open the template in Excel, edit, save, and extract again; the binary is the build artefact and the template is its source. If you also want the VBA source in version control, where it can be reviewed and diffed, see ",[19,453,455],{"href":454},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fextract-vba-macro-code-from-an-excel-file\u002F","Extract VBA Macro Code from an Excel File",[25,457,459],{"id":458},"step-2-attach-it-to-a-new-workbook","Step 2 — Attach it to a new workbook",[10,461,462,465,466,468],{},[14,463,464],{},"add_vba_project()"," takes the path to the binary. The file name must end in ",[14,467,167],{},":",[30,470,472],{"className":318,"code":471,"language":320,"meta":35,"style":35},"import xlsxwriter\n\nwb = xlsxwriter.Workbook(\"weekly_report.xlsm\")\nws = wb.add_worksheet(\"Report\")\nws.write_row(\"A1\", [\"Region\", \"Sales\"])\nfor row, (region, sales) in enumerate([(\"North\", 1250), (\"South\", 980), (\"West\", 1430)], start=1):\n    ws.write_row(row, 0, [region, sales])\nprint(\"add_vba_project returned\", wb.add_vba_project(\"vbaProject.bin\"))\nwb.close()\n",[14,473,474,481,485,500,515,537,598,608,626],{"__ignoreMap":35},[39,475,476,478],{"class":41,"line":42},[39,477,328],{"class":327},[39,479,480],{"class":291}," xlsxwriter\n",[39,482,483],{"class":41,"line":334},[39,484,338],{"emptyLinePlaceholder":337},[39,486,487,490,492,495,498],{"class":41,"line":341},[39,488,489],{"class":291},"wb ",[39,491,368],{"class":327},[39,493,494],{"class":291}," xlsxwriter.Workbook(",[39,496,497],{"class":49},"\"weekly_report.xlsm\"",[39,499,377],{"class":291},[39,501,502,505,507,510,513],{"class":41,"line":362},[39,503,504],{"class":291},"ws ",[39,506,368],{"class":327},[39,508,509],{"class":291}," wb.add_worksheet(",[39,511,512],{"class":49},"\"Report\"",[39,514,377],{"class":291},[39,516,517,520,523,526,529,531,534],{"class":41,"line":380},[39,518,519],{"class":291},"ws.write_row(",[39,521,522],{"class":49},"\"A1\"",[39,524,525],{"class":291},", [",[39,527,528],{"class":49},"\"Region\"",[39,530,394],{"class":291},[39,532,533],{"class":49},"\"Sales\"",[39,535,536],{"class":291},"])\n",[39,538,539,542,545,548,551,554,557,559,562,565,568,570,573,575,578,580,583,586,590,592,595],{"class":41,"line":407},[39,540,541],{"class":327},"for",[39,543,544],{"class":291}," row, (region, sales) ",[39,546,547],{"class":327},"in",[39,549,550],{"class":284}," enumerate",[39,552,553],{"class":291},"([(",[39,555,556],{"class":49},"\"North\"",[39,558,394],{"class":291},[39,560,561],{"class":284},"1250",[39,563,564],{"class":291},"), (",[39,566,567],{"class":49},"\"South\"",[39,569,394],{"class":291},[39,571,572],{"class":284},"980",[39,574,564],{"class":291},[39,576,577],{"class":49},"\"West\"",[39,579,394],{"class":291},[39,581,582],{"class":284},"1430",[39,584,585],{"class":291},")], ",[39,587,589],{"class":588},"sa561","start",[39,591,368],{"class":327},[39,593,594],{"class":284},"1",[39,596,597],{"class":291},"):\n",[39,599,600,603,605],{"class":41,"line":413},[39,601,602],{"class":291},"    ws.write_row(row, ",[39,604,100],{"class":284},[39,606,607],{"class":291},", [region, sales])\n",[39,609,611,613,615,618,621,623],{"class":41,"line":610},8,[39,612,416],{"class":284},[39,614,388],{"class":291},[39,616,617],{"class":49},"\"add_vba_project returned\"",[39,619,620],{"class":291},", wb.add_vba_project(",[39,622,391],{"class":49},[39,624,625],{"class":291},"))\n",[39,627,629],{"class":41,"line":628},9,[39,630,631],{"class":291},"wb.close()\n",[10,633,634],{},"It returns 0 when the file exists and -1, with a warning, when the path is wrong. The warning is easy to miss in a scheduled job, so check the return value and stop the job (here it just prints; in production, raise):",[30,636,638],{"className":318,"code":637,"language":320,"meta":35,"style":35},"wb = xlsxwriter.Workbook(\"broken.xlsm\")\nwb.add_worksheet()\nif wb.add_vba_project(\"wrong\u002FvbaProject.bin\") != 0:\n    print(\"VBA project not found - refusing to ship a report without its macros\")\nwb.close()\n",[14,639,640,653,658,680,692],{"__ignoreMap":35},[39,641,642,644,646,648,651],{"class":41,"line":42},[39,643,489],{"class":291},[39,645,368],{"class":327},[39,647,494],{"class":291},[39,649,650],{"class":49},"\"broken.xlsm\"",[39,652,377],{"class":291},[39,654,655],{"class":41,"line":334},[39,656,657],{"class":291},"wb.add_worksheet()\n",[39,659,660,663,666,669,671,674,677],{"class":41,"line":341},[39,661,662],{"class":327},"if",[39,664,665],{"class":291}," wb.add_vba_project(",[39,667,668],{"class":49},"\"wrong\u002FvbaProject.bin\"",[39,670,353],{"class":291},[39,672,673],{"class":327},"!=",[39,675,676],{"class":284}," 0",[39,678,679],{"class":291},":\n",[39,681,682,685,687,690],{"class":41,"line":362},[39,683,684],{"class":284},"    print",[39,686,388],{"class":291},[39,688,689],{"class":49},"\"VBA project not found - refusing to ship a report without its macros\"",[39,691,377],{"class":291},[39,693,694],{"class":41,"line":380},[39,695,631],{"class":291},[10,697,698,699,702,703,705,706,23],{},"xlsxwriter does not check the extension. Saved as ",[14,700,701],{},".xlsx",", the file still declares macro-enabled content, and Excel refuses to open it because the format and the extension do not match. Always use ",[14,704,167],{}," when calling ",[14,707,464],{},[78,709,87,715,87,718,87,721,87,726,87,732,87,738,87,742,87,746,87,750,87,755,87,757,87,760,87,763,87,766,87,768,87,771,87,774,87,778,87,780,87,783,87,786,87,790,87,792,87,795,87,798,87,802,87,804],{"viewBox":710,"role":81,"ariaLabelledBy":711,"xmlns":85,"style":714},"206 0 347 200",[712,713],"xv-parts-t","xv-parts-d","width:100%;max-width:347px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[89,716,717],{"id":712},"What changes inside the file",[93,719,720],{"id":713},"A table of the zip parts in the generated workbook. Workbook.xml switches to the macro-enabled content type and gains a code name, each sheet gains a code name, the vbaProject.bin part is added, and a vmlDrawing part is added only when a button is inserted.",[97,722],{"x":723,"y":100,"width":724,"height":725,"fill":103},"206","347","200",[97,727],{"x":728,"y":107,"width":729,"height":730,"rx":100,"fill":181,"stroke":146,"style":731},"222.9","130.1","28.0","stroke-width:1.5px",[126,733,737],{"x":734,"y":735,"style":736},"287.9","35.0","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Part",[97,739],{"x":740,"y":107,"width":741,"height":730,"rx":100,"fill":181,"stroke":146,"style":731},"353.0","184.2",[126,743,745],{"x":744,"y":735,"style":736},"445.0","Change",[97,747],{"x":728,"y":748,"width":729,"height":730,"rx":100,"fill":103,"stroke":112,"style":749},"44.0","stroke-width:1px",[126,751,754],{"x":734,"y":752,"style":753},"63.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","workbook.xml",[97,756],{"x":740,"y":748,"width":741,"height":730,"rx":100,"fill":103,"stroke":112,"style":749},[126,758,759],{"x":744,"y":752,"style":753},"macro-enabled type",[97,761],{"x":728,"y":762,"width":729,"height":730,"rx":100,"fill":103,"stroke":112,"style":749},"72.0",[126,764,754],{"x":734,"y":765,"style":753},"91.0",[97,767],{"x":740,"y":762,"width":741,"height":730,"rx":100,"fill":103,"stroke":112,"style":749},[126,769,770],{"x":744,"y":765,"style":753},"codeName ThisWorkbook",[97,772],{"x":728,"y":773,"width":729,"height":730,"rx":100,"fill":103,"stroke":112,"style":749},"100.0",[126,775,777],{"x":734,"y":776,"style":753},"119.0","sheet1.xml",[97,779],{"x":740,"y":773,"width":741,"height":730,"rx":100,"fill":103,"stroke":112,"style":749},[126,781,782],{"x":744,"y":776,"style":753},"codeName Sheet1",[97,784],{"x":728,"y":785,"width":729,"height":730,"rx":100,"fill":237,"stroke":238,"style":731},"128.0",[126,787,193],{"x":734,"y":788,"style":789},"147.0","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[97,791],{"x":740,"y":785,"width":741,"height":730,"rx":100,"fill":237,"stroke":238,"style":731},[126,793,794],{"x":744,"y":788,"style":789},"added as is",[97,796],{"x":728,"y":797,"width":729,"height":730,"rx":100,"fill":103,"stroke":112,"style":749},"156.0",[126,799,801],{"x":734,"y":800,"style":753},"175.0","vmlDrawing1.vml",[97,803],{"x":740,"y":797,"width":741,"height":730,"rx":100,"fill":103,"stroke":112,"style":749},[126,805,806],{"x":744,"y":800,"style":753},"only with a button",[25,808,810],{"id":809},"step-3-add-a-button-that-runs-a-macro","Step 3 — Add a button that runs a macro",[10,812,813,814,817],{},"A form-control button is the usual way to let users run a macro. ",[14,815,816],{},"insert_button()"," places one over a cell and links it to a procedure by name:",[30,819,821],{"className":318,"code":820,"language":320,"meta":35,"style":35},"wb = xlsxwriter.Workbook(\"report_with_button.xlsm\")\nws = wb.add_worksheet(\"Report\")\nws.write(\"A1\", \"Press the button to run the macro\")\nws.insert_button(\"B3\", {\n    \"macro\": \"say_hello\",\n    \"caption\": \"Say hello\",\n    \"width\": 120,\n    \"height\": 30,\n})\nwb.add_vba_project(\"vbaProject.bin\")\nwb.close()\n",[14,822,823,836,848,862,873,887,899,911,923,928,938],{"__ignoreMap":35},[39,824,825,827,829,831,834],{"class":41,"line":42},[39,826,489],{"class":291},[39,828,368],{"class":327},[39,830,494],{"class":291},[39,832,833],{"class":49},"\"report_with_button.xlsm\"",[39,835,377],{"class":291},[39,837,838,840,842,844,846],{"class":41,"line":334},[39,839,504],{"class":291},[39,841,368],{"class":327},[39,843,509],{"class":291},[39,845,512],{"class":49},[39,847,377],{"class":291},[39,849,850,853,855,857,860],{"class":41,"line":341},[39,851,852],{"class":291},"ws.write(",[39,854,522],{"class":49},[39,856,394],{"class":291},[39,858,859],{"class":49},"\"Press the button to run the macro\"",[39,861,377],{"class":291},[39,863,864,867,870],{"class":41,"line":362},[39,865,866],{"class":291},"ws.insert_button(",[39,868,869],{"class":49},"\"B3\"",[39,871,872],{"class":291},", {\n",[39,874,875,878,881,884],{"class":41,"line":380},[39,876,877],{"class":49},"    \"macro\"",[39,879,880],{"class":291},": ",[39,882,883],{"class":49},"\"say_hello\"",[39,885,886],{"class":291},",\n",[39,888,889,892,894,897],{"class":41,"line":407},[39,890,891],{"class":49},"    \"caption\"",[39,893,880],{"class":291},[39,895,896],{"class":49},"\"Say hello\"",[39,898,886],{"class":291},[39,900,901,904,906,909],{"class":41,"line":413},[39,902,903],{"class":49},"    \"width\"",[39,905,880],{"class":291},[39,907,908],{"class":284},"120",[39,910,886],{"class":291},[39,912,913,916,918,921],{"class":41,"line":610},[39,914,915],{"class":49},"    \"height\"",[39,917,880],{"class":291},[39,919,920],{"class":284},"30",[39,922,886],{"class":291},[39,924,925],{"class":41,"line":628},[39,926,927],{"class":291},"})\n",[39,929,931,934,936],{"class":41,"line":930},10,[39,932,933],{"class":291},"wb.add_vba_project(",[39,935,391],{"class":49},[39,937,377],{"class":291},[39,939,941],{"class":41,"line":940},11,[39,942,631],{"class":291},[10,944,945,946,949,950,953,954,957,958,961],{},"The ",[14,947,948],{},"macro"," value is the name of a public ",[14,951,952],{},"Sub"," in a standard module. Without it, xlsxwriter links the button to ",[14,955,956],{},"Button1_Click",", which almost certainly does not exist in your project. The options dictionary is not validated: a misspelled key such as ",[14,959,960],{},"\"marco\""," is silently ignored, leaving you with the default, so read the result back as in Step 5.",[78,963,87,968,87,971,87,974,87,977,87,982,87,987,87,991,87,996,87,1003,87,1006,87,1033,87,1039,87,1043,87,1047,87,1049,87,1065,87,1069,87,1073,87,1075,87,1090,87,1093,87,1109,87,1113,87,1117,87,1119,87,1134,87,1138,87,1145,87,1151,87,1155,87,1158,87,1162,87,1165,87,1168,87,1170,87,1175,87,1179,87,1184,87,1187,87,1189,87,1193,87,1196,87,1199,87,1202],{"viewBox":964,"role":81,"ariaLabelledBy":965,"xmlns":85,"style":86},"4 0 752 322",[966,967],"xv-button-t","xv-button-d",[89,969,970],{"id":966},"The pieces of a macro button",[93,972,973],{"id":967},"The insert_button call with its options. The macro key must match a public Sub in a standard module, the caption is the label users see, and width and height are in pixels.",[97,975],{"x":99,"y":100,"width":101,"height":976,"fill":103},"322",[97,978],{"x":106,"y":107,"width":979,"height":980,"rx":110,"fill":981,"stroke":112,"style":731},"720.0","170.0","#f0f2f5",[126,983,986],{"x":984,"y":984,"style":985},"36.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:start;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","report.py",[41,988],{"x1":106,"y1":989,"x2":990,"y2":989,"stroke":112,"style":749},"46.0","740.0",[126,992,594],{"x":993,"y":994,"style":995},"60.0","67.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",[997,998],"circle",{"cx":984,"cy":999,"r":1000,"fill":1001,"stroke":122,"style":1002},"62.5","11.0","#5b6780","stroke-width:0px",[126,1004,594],{"x":984,"y":994,"style":1005},"font-size:12px;font-weight:700;fill:#ffffff;text-anchor:middle",[126,1007,1010,1015,1017,1020,1022,1025,1028,1031],{"x":1008,"y":994,"style":1009},"82.0","font-size:12.5px;text-anchor:start;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",[1011,1012,1014],"tspan",{"style":1013},"fill:var(--text,#172033);font-weight:400","ws",[1011,1016,23],{"style":1013},[1011,1018,1019],{"style":1013},"insert_button",[1011,1021,388],{"style":1013},[1011,1023,869],{"style":1024},"fill:var(--teal-ink,#0b6157);font-weight:400",[1011,1026,1027],{"style":1013},",",[1011,1029,1030],{"style":1013}," ",[1011,1032,428],{"style":1013},[97,1034],{"x":1035,"y":1036,"width":1037,"height":1038,"rx":99,"fill":181,"stroke":146,"style":1002},"24.0","73.0","712.0","21.0",[126,1040,1042],{"x":993,"y":1041,"style":995},"88.0","2",[997,1044],{"cx":984,"cy":1045,"r":1000,"fill":1046,"stroke":122,"style":1002},"83.5","#4338ca",[126,1048,1042],{"x":984,"y":1041,"style":1005},[126,1050,1051,1054,1057,1059,1061,1063],{"x":1008,"y":1041,"style":1009},[1011,1052,1053],{"style":1013},"    ",[1011,1055,1056],{"style":1024},"\"macro\"",[1011,1058,468],{"style":1013},[1011,1060,1030],{"style":1013},[1011,1062,883],{"style":1024},[1011,1064,1027],{"style":1013},[126,1066,1068],{"x":993,"y":1067,"style":995},"109.0","3",[997,1070],{"cx":984,"cy":1071,"r":1000,"fill":1072,"stroke":122,"style":1002},"104.5","#0b6157",[126,1074,1068],{"x":984,"y":1067,"style":1005},[126,1076,1077,1079,1082,1084,1086,1088],{"x":1008,"y":1067,"style":1009},[1011,1078,1053],{"style":1013},[1011,1080,1081],{"style":1024},"\"caption\"",[1011,1083,468],{"style":1013},[1011,1085,1030],{"style":1013},[1011,1087,896],{"style":1024},[1011,1089,1027],{"style":1013},[126,1091,99],{"x":993,"y":1092,"style":995},"130.0",[126,1094,1095,1097,1100,1102,1104,1107],{"x":1008,"y":1092,"style":1009},[1011,1096,1053],{"style":1013},[1011,1098,1099],{"style":1024},"\"width\"",[1011,1101,468],{"style":1013},[1011,1103,1030],{"style":1013},[1011,1105,908],{"style":1106},"fill:var(--gold-ink,#7a4e06);font-weight:400",[1011,1108,1027],{"style":1013},[126,1110,1112],{"x":993,"y":1111,"style":995},"151.0","5",[997,1114],{"cx":984,"cy":1115,"r":1000,"fill":1116,"stroke":122,"style":1002},"146.5","#8a5808",[126,1118,99],{"x":984,"y":1111,"style":1005},[126,1120,1121,1123,1126,1128,1130,1132],{"x":1008,"y":1111,"style":1009},[1011,1122,1053],{"style":1013},[1011,1124,1125],{"style":1024},"\"height\"",[1011,1127,468],{"style":1013},[1011,1129,1030],{"style":1013},[1011,1131,920],{"style":1106},[1011,1133,1027],{"style":1013},[126,1135,1137],{"x":993,"y":1136,"style":995},"172.0","6",[126,1139,1140,1142],{"x":1008,"y":1136,"style":1009},[1011,1141,440],{"style":1013},[1011,1143,1144],{"style":1013},")",[97,1146],{"x":106,"y":1147,"width":1148,"height":1149,"rx":1150,"fill":111,"stroke":112,"style":731},"200.0","230.7","52.0","10",[997,1152],{"cx":1153,"cy":1154,"r":1000,"fill":1001,"stroke":122,"style":1002},"40.0","226.0",[126,1156,594],{"x":1153,"y":1157,"style":1005},"230.5",[126,1159,1161],{"x":993,"y":1157,"style":1160},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","top-left cell of the button",[97,1163],{"x":1164,"y":1147,"width":1148,"height":1149,"rx":1150,"fill":181,"stroke":146,"style":731},"264.7",[997,1166],{"cx":1167,"cy":1154,"r":1000,"fill":1046,"stroke":122,"style":1002},"284.7",[126,1169,1042],{"x":1167,"y":1157,"style":1005},[126,1171,1174],{"x":1172,"y":1173,"style":1160},"304.7","222.0","a public Sub in a standard",[126,1176,1178],{"x":1172,"y":1177,"style":1160},"239.0","module",[97,1180],{"x":1181,"y":1147,"width":1148,"height":1149,"rx":1150,"fill":1182,"stroke":1183,"style":731},"509.3","#d9f4f1","var(--teal,#0f9488)",[997,1185],{"cx":1186,"cy":1154,"r":1000,"fill":1072,"stroke":122,"style":1002},"529.3",[126,1188,1068],{"x":1186,"y":1157,"style":1005},[126,1190,1192],{"x":1191,"y":1157,"style":1160},"549.3","the label users see",[97,1194],{"x":106,"y":1195,"width":1148,"height":748,"rx":1150,"fill":237,"stroke":238,"style":731},"262.0",[997,1197],{"cx":1153,"cy":1198,"r":1000,"fill":1116,"stroke":122,"style":1002},"284.0",[126,1200,99],{"x":1153,"y":1201,"style":1005},"288.5",[126,1203,1204],{"x":993,"y":1201,"style":1160},"size in pixels",[25,1206,1208],{"id":1207},"step-4-keep-the-code-names-consistent","Step 4 — Keep the code names consistent",[10,1210,1211,1212,880,1216,394,1219,394,1222,1225,1226,1228,1229,1231,1232,1234,1235,394,1237,1239],{},"VBA refers to the workbook and its sheets by ",[1213,1214,1215],"em",{},"code names",[14,1217,1218],{},"ThisWorkbook",[14,1220,1221],{},"Sheet1",[14,1223,1224],{},"Sheet2",". Event handlers such as ",[14,1227,16],{}," live in the ",[14,1230,1218],{}," module and only fire if the workbook's code name matches the module in the project. xlsxwriter sets ",[14,1233,1218],{}," and ",[14,1236,1221],{},[14,1238,1224],{},"… by default once a VBA project is added:",[30,1241,1243],{"className":318,"code":1242,"language":320,"meta":35,"style":35},"import re\nimport zipfile\n\nwb = xlsxwriter.Workbook(\"two_sheets.xlsm\")\nwb.add_worksheet(\"Data\")\nwb.add_worksheet(\"Summary\")\nwb.add_vba_project(\"vbaProject.bin\")\nwb.close()\n\nwith zipfile.ZipFile(\"two_sheets.xlsm\") as z:\n    for part in (\"xl\u002Fworkbook.xml\", \"xl\u002Fworksheets\u002Fsheet1.xml\", \"xl\u002Fworksheets\u002Fsheet2.xml\"):\n        names = re.findall(r'codeName=\"(\\w+)\"', z.read(part).decode())\n        print(f\"{part:26} {names}\")\n",[14,1244,1245,1252,1258,1262,1275,1285,1294,1302,1306,1310,1324,1352,1383],{"__ignoreMap":35},[39,1246,1247,1249],{"class":41,"line":42},[39,1248,328],{"class":327},[39,1250,1251],{"class":291}," re\n",[39,1253,1254,1256],{"class":41,"line":334},[39,1255,328],{"class":327},[39,1257,331],{"class":291},[39,1259,1260],{"class":41,"line":341},[39,1261,338],{"emptyLinePlaceholder":337},[39,1263,1264,1266,1268,1270,1273],{"class":41,"line":362},[39,1265,489],{"class":291},[39,1267,368],{"class":327},[39,1269,494],{"class":291},[39,1271,1272],{"class":49},"\"two_sheets.xlsm\"",[39,1274,377],{"class":291},[39,1276,1277,1280,1283],{"class":41,"line":380},[39,1278,1279],{"class":291},"wb.add_worksheet(",[39,1281,1282],{"class":49},"\"Data\"",[39,1284,377],{"class":291},[39,1286,1287,1289,1292],{"class":41,"line":407},[39,1288,1279],{"class":291},[39,1290,1291],{"class":49},"\"Summary\"",[39,1293,377],{"class":291},[39,1295,1296,1298,1300],{"class":41,"line":413},[39,1297,933],{"class":291},[39,1299,391],{"class":49},[39,1301,377],{"class":291},[39,1303,1304],{"class":41,"line":610},[39,1305,631],{"class":291},[39,1307,1308],{"class":41,"line":628},[39,1309,338],{"emptyLinePlaceholder":337},[39,1311,1312,1314,1316,1318,1320,1322],{"class":41,"line":930},[39,1313,344],{"class":327},[39,1315,347],{"class":291},[39,1317,1272],{"class":49},[39,1319,353],{"class":291},[39,1321,356],{"class":327},[39,1323,359],{"class":291},[39,1325,1326,1329,1332,1334,1337,1340,1342,1345,1347,1350],{"class":41,"line":940},[39,1327,1328],{"class":327},"    for",[39,1330,1331],{"class":291}," part ",[39,1333,547],{"class":327},[39,1335,1336],{"class":291}," (",[39,1338,1339],{"class":49},"\"xl\u002Fworkbook.xml\"",[39,1341,394],{"class":291},[39,1343,1344],{"class":49},"\"xl\u002Fworksheets\u002Fsheet1.xml\"",[39,1346,394],{"class":291},[39,1348,1349],{"class":49},"\"xl\u002Fworksheets\u002Fsheet2.xml\"",[39,1351,597],{"class":291},[39,1353,1355,1358,1360,1363,1366,1369,1372,1375,1377,1380],{"class":41,"line":1354},12,[39,1356,1357],{"class":291},"        names ",[39,1359,368],{"class":327},[39,1361,1362],{"class":291}," re.findall(",[39,1364,1365],{"class":327},"r",[39,1367,1368],{"class":49},"'codeName=\"",[39,1370,1371],{"class":284},"(\\w",[39,1373,1374],{"class":327},"+",[39,1376,1144],{"class":284},[39,1378,1379],{"class":49},"\"'",[39,1381,1382],{"class":291},", z.read(part).decode())\n",[39,1384,1386,1389,1391,1393,1395,1397,1400,1403,1405,1408,1411,1413,1415],{"class":41,"line":1385},13,[39,1387,1388],{"class":284},"        print",[39,1390,388],{"class":291},[39,1392,421],{"class":327},[39,1394,424],{"class":49},[39,1396,428],{"class":427},[39,1398,1399],{"class":291},"part",[39,1401,1402],{"class":327},":26",[39,1404,440],{"class":427},[39,1406,1407],{"class":427}," {",[39,1409,1410],{"class":291},"names",[39,1412,440],{"class":427},[39,1414,424],{"class":49},[39,1416,377],{"class":291},[10,1418,1419,1420,1234,1423,1426],{},"Those defaults match a project saved from an English-language Excel. A template saved in a localised Excel uses translated names, for example ",[14,1421,1422],{},"DieseArbeitsmappe",[14,1424,1425],{},"Tabelle1"," in German, and code that refers to a sheet by its code name expects the same. Set them explicitly to whatever the project uses:",[30,1428,1430],{"className":318,"code":1429,"language":320,"meta":35,"style":35},"wb = xlsxwriter.Workbook(\"german_template.xlsm\")\nwb.set_vba_name(\"DieseArbeitsmappe\")\nws = wb.add_worksheet(\"Daten\")\nws.set_vba_name(\"Tabelle1\")\nwb.add_vba_project(\"vbaProject.bin\")\nwb.close()\n",[14,1431,1432,1445,1455,1468,1478,1486],{"__ignoreMap":35},[39,1433,1434,1436,1438,1440,1443],{"class":41,"line":42},[39,1435,489],{"class":291},[39,1437,368],{"class":327},[39,1439,494],{"class":291},[39,1441,1442],{"class":49},"\"german_template.xlsm\"",[39,1444,377],{"class":291},[39,1446,1447,1450,1453],{"class":41,"line":334},[39,1448,1449],{"class":291},"wb.set_vba_name(",[39,1451,1452],{"class":49},"\"DieseArbeitsmappe\"",[39,1454,377],{"class":291},[39,1456,1457,1459,1461,1463,1466],{"class":41,"line":341},[39,1458,504],{"class":291},[39,1460,368],{"class":327},[39,1462,509],{"class":291},[39,1464,1465],{"class":49},"\"Daten\"",[39,1467,377],{"class":291},[39,1469,1470,1473,1476],{"class":41,"line":362},[39,1471,1472],{"class":291},"ws.set_vba_name(",[39,1474,1475],{"class":49},"\"Tabelle1\"",[39,1477,377],{"class":291},[39,1479,1480,1482,1484],{"class":41,"line":380},[39,1481,933],{"class":291},[39,1483,391],{"class":49},[39,1485,377],{"class":291},[39,1487,1488],{"class":41,"line":407},[39,1489,631],{"class":291},[10,1491,1492,1493,1496],{},"The names to use are the module names you see in the VBA editor's project tree, or the ",[14,1494,1495],{},".cls"," module names that oletools lists when it reads the template.",[25,1498,1500],{"id":1499},"step-5-check-the-macro-is-in-the-file","Step 5 — Check the macro is in the file",[10,1502,1503],{},"Nobody wants to find out from a user that the button does nothing. Verify the output in the same job, without Excel: the part must be present, the button must point at the right macro, and the procedure must be in the project.",[30,1505,1507],{"className":318,"code":1506,"language":320,"meta":35,"style":35},"from oletools.olevba import VBA_Parser\n\ndef check_macro_workbook(path, macro):\n    with zipfile.ZipFile(path) as z:\n        names = z.namelist()\n        assert \"xl\u002FvbaProject.bin\" in names, \"no VBA project\"\n        vml = \"\".join(z.read(n).decode() for n in names if n.endswith(\".vml\"))\n    buttons = re.findall(r\"\u003Cx:FmlaMacro>\\[0\\]!(\\w+)\", vml)\n    parser = VBA_Parser(path)\n    code = \"\\n\".join(src for _, _, _, src in parser.extract_macros())\n    parser.close()\n    procedures = re.findall(r\"^\\s*(?:Public\\s+)?Sub\\s+(\\w+)\", code, re.MULTILINE | re.IGNORECASE)\n    print(f\"buttons -> {buttons}; procedures in project -> {procedures}\")\n    missing = [b for b in buttons if b not in procedures]\n    assert macro in procedures, f\"{macro} is not in the project\"\n    assert not missing, f\"buttons point at missing macros: {missing}\"\n    return \"ok\"\n\nprint(check_macro_workbook(\"report_with_button.xlsm\", \"say_hello\"))\n",[14,1508,1509,1522,1526,1538,1550,1559,1576,1609,1646,1656,1684,1689,1755,1787,1820,1847,1873,1882,1887],{"__ignoreMap":35},[39,1510,1511,1514,1517,1519],{"class":41,"line":42},[39,1512,1513],{"class":327},"from",[39,1515,1516],{"class":291}," oletools.olevba ",[39,1518,328],{"class":327},[39,1520,1521],{"class":284}," VBA_Parser\n",[39,1523,1524],{"class":41,"line":334},[39,1525,338],{"emptyLinePlaceholder":337},[39,1527,1528,1531,1535],{"class":41,"line":341},[39,1529,1530],{"class":327},"def",[39,1532,1534],{"class":1533},"s_Opv"," check_macro_workbook",[39,1536,1537],{"class":291},"(path, macro):\n",[39,1539,1540,1543,1546,1548],{"class":41,"line":362},[39,1541,1542],{"class":327},"    with",[39,1544,1545],{"class":291}," zipfile.ZipFile(path) ",[39,1547,356],{"class":327},[39,1549,359],{"class":291},[39,1551,1552,1554,1556],{"class":41,"line":380},[39,1553,1357],{"class":291},[39,1555,368],{"class":327},[39,1557,1558],{"class":291}," z.namelist()\n",[39,1560,1561,1564,1567,1570,1573],{"class":41,"line":407},[39,1562,1563],{"class":327},"        assert",[39,1565,1566],{"class":49}," \"xl\u002FvbaProject.bin\"",[39,1568,1569],{"class":327}," in",[39,1571,1572],{"class":291}," names, ",[39,1574,1575],{"class":49},"\"no VBA project\"\n",[39,1577,1578,1581,1583,1586,1589,1591,1594,1596,1599,1601,1604,1607],{"class":41,"line":413},[39,1579,1580],{"class":291},"        vml ",[39,1582,368],{"class":327},[39,1584,1585],{"class":49}," \"\"",[39,1587,1588],{"class":291},".join(z.read(n).decode() ",[39,1590,541],{"class":327},[39,1592,1593],{"class":291}," n ",[39,1595,547],{"class":327},[39,1597,1598],{"class":291}," names ",[39,1600,662],{"class":327},[39,1602,1603],{"class":291}," n.endswith(",[39,1605,1606],{"class":49},"\".vml\"",[39,1608,625],{"class":291},[39,1610,1611,1614,1616,1618,1620,1623,1627,1629,1632,1635,1637,1639,1641,1643],{"class":41,"line":610},[39,1612,1613],{"class":291},"    buttons ",[39,1615,368],{"class":327},[39,1617,1362],{"class":291},[39,1619,1365],{"class":327},[39,1621,1622],{"class":49},"\"\u003Cx:FmlaMacro>",[39,1624,1626],{"class":1625},"s_b0D","\\[",[39,1628,100],{"class":49},[39,1630,1631],{"class":1625},"\\]",[39,1633,1634],{"class":49},"!",[39,1636,1371],{"class":284},[39,1638,1374],{"class":327},[39,1640,1144],{"class":284},[39,1642,424],{"class":49},[39,1644,1645],{"class":291},", vml)\n",[39,1647,1648,1651,1653],{"class":41,"line":628},[39,1649,1650],{"class":291},"    parser ",[39,1652,368],{"class":327},[39,1654,1655],{"class":291}," VBA_Parser(path)\n",[39,1657,1658,1661,1663,1666,1669,1671,1674,1676,1679,1681],{"class":41,"line":930},[39,1659,1660],{"class":291},"    code ",[39,1662,368],{"class":327},[39,1664,1665],{"class":49}," \"",[39,1667,1668],{"class":427},"\\n",[39,1670,424],{"class":49},[39,1672,1673],{"class":291},".join(src ",[39,1675,541],{"class":327},[39,1677,1678],{"class":291}," _, _, _, src ",[39,1680,547],{"class":327},[39,1682,1683],{"class":291}," parser.extract_macros())\n",[39,1685,1686],{"class":41,"line":940},[39,1687,1688],{"class":291},"    parser.close()\n",[39,1690,1691,1694,1696,1698,1700,1702,1705,1708,1711,1714,1717,1719,1721,1724,1726,1728,1730,1732,1734,1736,1738,1741,1744,1747,1750,1753],{"class":41,"line":1354},[39,1692,1693],{"class":291},"    procedures ",[39,1695,368],{"class":327},[39,1697,1362],{"class":291},[39,1699,1365],{"class":327},[39,1701,424],{"class":49},[39,1703,1704],{"class":284},"^\\s",[39,1706,1707],{"class":327},"*",[39,1709,1710],{"class":284},"(?:",[39,1712,1713],{"class":49},"Public",[39,1715,1716],{"class":284},"\\s",[39,1718,1374],{"class":327},[39,1720,1144],{"class":284},[39,1722,1723],{"class":327},"?",[39,1725,952],{"class":49},[39,1727,1716],{"class":284},[39,1729,1374],{"class":327},[39,1731,1371],{"class":284},[39,1733,1374],{"class":327},[39,1735,1144],{"class":284},[39,1737,424],{"class":49},[39,1739,1740],{"class":291},", code, re.",[39,1742,1743],{"class":284},"MULTILINE",[39,1745,1746],{"class":327}," |",[39,1748,1749],{"class":291}," re.",[39,1751,1752],{"class":284},"IGNORECASE",[39,1754,377],{"class":291},[39,1756,1757,1759,1761,1763,1766,1768,1771,1773,1776,1778,1781,1783,1785],{"class":41,"line":1385},[39,1758,684],{"class":284},[39,1760,388],{"class":291},[39,1762,421],{"class":327},[39,1764,1765],{"class":49},"\"buttons -> ",[39,1767,428],{"class":427},[39,1769,1770],{"class":291},"buttons",[39,1772,440],{"class":427},[39,1774,1775],{"class":49},"; procedures in project -> ",[39,1777,428],{"class":427},[39,1779,1780],{"class":291},"procedures",[39,1782,440],{"class":427},[39,1784,424],{"class":49},[39,1786,377],{"class":291},[39,1788,1790,1793,1795,1798,1800,1803,1805,1808,1810,1812,1815,1817],{"class":41,"line":1789},14,[39,1791,1792],{"class":291},"    missing ",[39,1794,368],{"class":327},[39,1796,1797],{"class":291}," [b ",[39,1799,541],{"class":327},[39,1801,1802],{"class":291}," b ",[39,1804,547],{"class":327},[39,1806,1807],{"class":291}," buttons ",[39,1809,662],{"class":327},[39,1811,1802],{"class":291},[39,1813,1814],{"class":327},"not",[39,1816,1569],{"class":327},[39,1818,1819],{"class":291}," procedures]\n",[39,1821,1823,1826,1829,1831,1834,1836,1838,1840,1842,1844],{"class":41,"line":1822},15,[39,1824,1825],{"class":327},"    assert",[39,1827,1828],{"class":291}," macro ",[39,1830,547],{"class":327},[39,1832,1833],{"class":291}," procedures, ",[39,1835,421],{"class":327},[39,1837,424],{"class":49},[39,1839,428],{"class":427},[39,1841,948],{"class":291},[39,1843,440],{"class":427},[39,1845,1846],{"class":49}," is not in the project\"\n",[39,1848,1850,1852,1855,1858,1860,1863,1865,1868,1870],{"class":41,"line":1849},16,[39,1851,1825],{"class":327},[39,1853,1854],{"class":327}," not",[39,1856,1857],{"class":291}," missing, ",[39,1859,421],{"class":327},[39,1861,1862],{"class":49},"\"buttons point at missing macros: ",[39,1864,428],{"class":427},[39,1866,1867],{"class":291},"missing",[39,1869,440],{"class":427},[39,1871,1872],{"class":49},"\"\n",[39,1874,1876,1879],{"class":41,"line":1875},17,[39,1877,1878],{"class":327},"    return",[39,1880,1881],{"class":49}," \"ok\"\n",[39,1883,1885],{"class":41,"line":1884},18,[39,1886,338],{"emptyLinePlaceholder":337},[39,1888,1890,1892,1895,1897,1899,1901],{"class":41,"line":1889},19,[39,1891,416],{"class":284},[39,1893,1894],{"class":291},"(check_macro_workbook(",[39,1896,833],{"class":49},[39,1898,394],{"class":291},[39,1900,883],{"class":49},[39,1902,625],{"class":291},[10,1904,1905,1906,1908],{},"The check catches the three common failures: a missing binary, a button linked to a macro that was renamed in the template, and a stale ",[14,1907,193],{}," that predates a new procedure.",[25,1910,1912],{"id":1911},"step-6-edit-the-file-later-without-losing-the-macros","Step 6 — Edit the file later without losing the macros",[10,1914,1915,1916,1919],{},"If another step opens the report with openpyxl, for example to add a sheet, pass ",[14,1917,1918],{},"keep_vba=True",". Without it, openpyxl drops the VBA project on save:",[30,1921,1923],{"className":318,"code":1922,"language":320,"meta":35,"style":35},"from openpyxl import load_workbook\n\nfor keep in (True, False):\n    wb = load_workbook(\"report_with_button.xlsm\", keep_vba=keep)\n    wb[\"Report\"][\"A2\"] = \"edited by openpyxl\"\n    out = f\"edited_keep_vba_{keep}.xlsm\"\n    wb.save(out)\n    with zipfile.ZipFile(out) as z:\n        print(f\"keep_vba={keep!s:5}  vbaProject.bin kept: {'xl\u002FvbaProject.bin' in z.namelist()}\")\n",[14,1924,1925,1937,1941,1962,1984,2005,2028,2033,2044],{"__ignoreMap":35},[39,1926,1927,1929,1932,1934],{"class":41,"line":42},[39,1928,1513],{"class":327},[39,1930,1931],{"class":291}," openpyxl ",[39,1933,328],{"class":327},[39,1935,1936],{"class":291}," load_workbook\n",[39,1938,1939],{"class":41,"line":334},[39,1940,338],{"emptyLinePlaceholder":337},[39,1942,1943,1945,1948,1950,1952,1955,1957,1960],{"class":41,"line":341},[39,1944,541],{"class":327},[39,1946,1947],{"class":291}," keep ",[39,1949,547],{"class":327},[39,1951,1336],{"class":291},[39,1953,1954],{"class":284},"True",[39,1956,394],{"class":291},[39,1958,1959],{"class":284},"False",[39,1961,597],{"class":291},[39,1963,1964,1967,1969,1972,1974,1976,1979,1981],{"class":41,"line":362},[39,1965,1966],{"class":291},"    wb ",[39,1968,368],{"class":327},[39,1970,1971],{"class":291}," load_workbook(",[39,1973,833],{"class":49},[39,1975,394],{"class":291},[39,1977,1978],{"class":588},"keep_vba",[39,1980,368],{"class":327},[39,1982,1983],{"class":291},"keep)\n",[39,1985,1986,1989,1991,1994,1997,2000,2002],{"class":41,"line":380},[39,1987,1988],{"class":291},"    wb[",[39,1990,512],{"class":49},[39,1992,1993],{"class":291},"][",[39,1995,1996],{"class":49},"\"A2\"",[39,1998,1999],{"class":291},"] ",[39,2001,368],{"class":327},[39,2003,2004],{"class":49}," \"edited by openpyxl\"\n",[39,2006,2007,2010,2012,2015,2018,2020,2023,2025],{"class":41,"line":407},[39,2008,2009],{"class":291},"    out ",[39,2011,368],{"class":327},[39,2013,2014],{"class":327}," f",[39,2016,2017],{"class":49},"\"edited_keep_vba_",[39,2019,428],{"class":427},[39,2021,2022],{"class":291},"keep",[39,2024,440],{"class":427},[39,2026,2027],{"class":49},".xlsm\"\n",[39,2029,2030],{"class":41,"line":413},[39,2031,2032],{"class":291},"    wb.save(out)\n",[39,2034,2035,2037,2040,2042],{"class":41,"line":610},[39,2036,1542],{"class":327},[39,2038,2039],{"class":291}," zipfile.ZipFile(out) ",[39,2041,356],{"class":327},[39,2043,359],{"class":291},[39,2045,2046,2048,2050,2052,2055,2057,2059,2062,2064,2067,2069,2072,2074,2077,2079,2081],{"class":41,"line":628},[39,2047,1388],{"class":284},[39,2049,388],{"class":291},[39,2051,421],{"class":327},[39,2053,2054],{"class":49},"\"keep_vba=",[39,2056,428],{"class":427},[39,2058,2022],{"class":291},[39,2060,2061],{"class":327},"!s:5",[39,2063,440],{"class":427},[39,2065,2066],{"class":49},"  vbaProject.bin kept: ",[39,2068,428],{"class":427},[39,2070,2071],{"class":49},"'xl\u002FvbaProject.bin'",[39,2073,1569],{"class":327},[39,2075,2076],{"class":291}," z.namelist()",[39,2078,440],{"class":427},[39,2080,424],{"class":49},[39,2082,377],{"class":291},[10,2084,2085,2089],{},[19,2086,2088],{"href":2087},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fwork-with-macro-enabled-xlsm-files-in-openpyxl\u002F","Work with Macro-Enabled .xlsm Files in openpyxl"," covers that route in more detail. The simpler design is to do all the writing in one xlsxwriter pass and attach the project last.",[78,2091,87,2096,87,2099,87,2102,87,2105,87,2109,87,2114,87,2117,87,2121,87,2125,87,2128,87,2131,87,2137,87,2142,87,2146,87,2151,87,2154,87,2157,87,2160,87,2163,87,2167,87,2170,87,2173,87,2180,87,2184,87,2186,87,2189,87,2192,87,2196,87,2199,87,2202,87,2208,87,2211,87,2214,87,2216,87,2219,87,2223,87,2226,87,2229,87,2234,87,2237],{"viewBox":2092,"role":81,"ariaLabelledBy":2093,"xmlns":85,"style":86},"4 0 752 272",[2094,2095],"xv-route-t","xv-route-d",[89,2097,2098],{"id":2094},"Which way to get macros into a Python report",[93,2100,2101],{"id":2095},"A decision diagram. For a new report written from scratch, attach vbaProject.bin with xlsxwriter. To change cells in an existing macro workbook, load it with openpyxl keep_vba=True. To run a macro rather than ship it, drive Excel with win32com or xlwings. If the logic can live in Python, drop the macro.",[97,2103],{"x":99,"y":100,"width":101,"height":2104,"fill":103},"272",[97,2106],{"x":2107,"y":107,"width":2108,"height":129,"rx":110,"fill":181,"stroke":146,"style":113},"240.0","280.0",[115,2110,2111],{"transform":184},[119,2112],{"d":2113,"fill":122,"stroke":188,"style":124},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5",[126,2115,2116],{"x":191,"y":129,"style":192},"What do you need?",[41,2118],{"x1":191,"y1":2119,"x2":191,"y2":2120,"stroke":146,"style":113},"91.5","111.5",[41,2122],{"x1":2123,"y1":2120,"x2":2124,"y2":2120,"stroke":146,"style":113},"104.0","656.0",[41,2126],{"x1":2123,"y1":2120,"x2":2123,"y2":2127,"stroke":146,"style":113},"157.5",[148,2129],{"points":2130,"fill":151},"104.0,161.5 99.0,152.5 109.0,152.5",[97,2132],{"x":2133,"y":2134,"width":2135,"height":1038,"rx":1150,"fill":103,"stroke":112,"style":2136},"62.9","121.5","82.3","stroke-width:1.2px",[126,2138,2141],{"x":2123,"y":2139,"style":2140},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","new report",[97,2143],{"x":106,"y":2144,"width":2145,"height":134,"rx":110,"fill":181,"stroke":146,"style":113},"163.5","168.0",[115,2147,2149],{"transform":2148},"translate(92.0,176.5) scale(1.000)",[119,2150],{"d":187,"fill":122,"stroke":188,"style":124},[126,2152,220],{"x":2123,"y":2153,"style":192},"223.0",[126,2155,226],{"x":2123,"y":2156,"style":139},"242.5",[41,2158],{"x1":2159,"y1":2120,"x2":2159,"y2":2127,"stroke":146,"style":113},"288.0",[148,2161],{"points":2162,"fill":151},"288.0,161.5 283.0,152.5 293.0,152.5",[97,2164],{"x":2165,"y":2134,"width":2166,"height":1038,"rx":1150,"fill":103,"stroke":112,"style":2136},"225.3","125.4",[126,2168,2169],{"x":2159,"y":2139,"style":2140},"edit existing .xlsm",[97,2171],{"x":2172,"y":2144,"width":2145,"height":134,"rx":110,"fill":1182,"stroke":1183,"style":113},"204.0",[115,2174,2176],{"transform":2175},"translate(276.0,176.5) scale(1.000)",[119,2177],{"d":2178,"fill":122,"stroke":2179,"style":124},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--teal-ink,#0b6157)",[126,2181,2183],{"x":2159,"y":2153,"style":2182},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","openpyxl",[126,2185,1918],{"x":2159,"y":2156,"style":139},[41,2187],{"x1":2188,"y1":2120,"x2":2188,"y2":2127,"stroke":146,"style":113},"472.0",[148,2190],{"points":2191,"fill":151},"472.0,161.5 467.0,152.5 477.0,152.5",[97,2193],{"x":2194,"y":2134,"width":2195,"height":1038,"rx":1150,"fill":103,"stroke":112,"style":2136},"421.9","100.3",[126,2197,2198],{"x":2188,"y":2139,"style":2140},"run the macro",[97,2200],{"x":2201,"y":2144,"width":2145,"height":134,"rx":110,"fill":237,"stroke":238,"style":113},"388.0",[115,2203,2205],{"transform":2204},"translate(460.0,176.5) scale(1.000)",[119,2206],{"d":2207,"fill":122,"stroke":245,"style":124},"M3 5.5l8-1.2v7.2H3z M13 4l8-1.2v8.7h-8z M3 13.5h8v7.2l-8-1.2z M13 13.5h8v8.7l-8-1.2z",[126,2209,2210],{"x":2188,"y":2153,"style":249},"win32com",[126,2212,2213],{"x":2188,"y":2156,"style":139},"Excel on Windows",[41,2215],{"x1":2124,"y1":2120,"x2":2124,"y2":2127,"stroke":146,"style":113},[148,2217],{"points":2218,"fill":151},"656.0,161.5 651.0,152.5 661.0,152.5",[97,2220],{"x":2221,"y":2134,"width":2222,"height":1038,"rx":1150,"fill":103,"stroke":112,"style":2136},"592.4","127.3",[126,2224,2225],{"x":2124,"y":2139,"style":2140},"logic fits in Python",[97,2227],{"x":2228,"y":2144,"width":2145,"height":134,"rx":110,"fill":111,"stroke":112,"style":113},"572.0",[115,2230,2232],{"transform":2231},"translate(644.0,176.5) scale(1.000)",[119,2233],{"d":121,"fill":122,"stroke":123,"style":124},[126,2235,2236],{"x":2124,"y":2153,"style":130},"No macro",[126,2238,2239],{"x":2124,"y":2156,"style":139},"port it",[25,2241,2243],{"id":2242},"when-not-to-ship-a-macro","When not to ship a macro",[10,2245,2246,2247,2251,2252,2256],{},"A macro in a generated report is code that runs on every recipient's machine, and many organisations block macros in files from the internet or email by default: users see a security banner, or nothing at all, and the button silently does nothing. Before adding one, ask whether the work could happen in Python before the file is written. Refreshing data, applying formatting, building a summary sheet and splitting a file per region are all easier to test in Python. ",[19,2248,2250],{"href":2249},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Ftranslate-common-vba-macros-to-python\u002F","Translate Common VBA Macros to Python"," shows the usual equivalents, and ",[19,2253,2255],{"href":2254},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fpython-vs-vba-for-excel-automation\u002F","Python vs VBA for Excel Automation"," weighs the two. Macros earn their place when the user needs to act inside Excel after the report arrives: re-running a calculation with their own inputs, or pushing a selection somewhere.",[10,2258,2259,2260,23],{},"If a macro only needs to run once, on your side, run it instead of shipping it: ",[19,2261,2263],{"href":2262},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frun-an-excel-macro-from-python-with-win32com\u002F","Run an Excel Macro from Python with win32com",[25,2265,2267],{"id":2266},"common-pitfalls","Common pitfalls",[2269,2270,2271,2281,2290,2302,2318,2324],"ul",{},[2272,2273,2274,2278,2279,23],"li",{},[2275,2276,2277],"strong",{},"Saving as .xlsx."," xlsxwriter writes the macro content type regardless of the name; Excel rejects the mismatch. Use ",[14,2280,167],{},[2272,2282,2283,2286,2287,23],{},[2275,2284,2285],{},"Ignoring the return value."," A wrong path produces a warning and a workbook without macros; check for ",[14,2288,2289],{},"-1",[2272,2291,2292,2295,2296,2298,2299,2301],{},[2275,2293,2294],{},"Button linked to the default macro."," Without ",[14,2297,948],{},", the button calls ",[14,2300,956],{},". Misspelled option keys are ignored.",[2272,2303,2304,2307,2308,2310,2311,2313,2314,2317],{},[2275,2305,2306],{},"Localised code names."," Event handlers in ",[14,2309,1422],{}," never fire in a workbook named ",[14,2312,1218],{},"; set ",[14,2315,2316],{},"set_vba_name()"," to match the template.",[2272,2319,2320,2323],{},[2275,2321,2322],{},"Round-tripping through openpyxl without keep_vba."," The project is dropped on save with no warning.",[2272,2325,2326,2329],{},[2275,2327,2328],{},"Editing the binary."," vbaProject.bin is compiled and compressed; change macros in Excel and extract again rather than patching the file.",[25,2331,2333],{"id":2332},"conclusion","Conclusion",[10,2335,2336,2337,2339,2340,2342,2343,2346,2347,2349,2350,2352,2353,2356,2357,2359,2360,2362],{},"Write macros once in Excel, extract ",[14,2338,269],{}," with ",[14,2341,273],{}," or ",[14,2344,2345],{},"zipfile",", and commit it with the report code. Attach it with ",[14,2348,464],{}," to a workbook named ",[14,2351,167],{},", check the return value, link buttons with ",[14,2354,2355],{},"insert_button({\"macro\": ...})",", and set code names with ",[14,2358,2316],{}," when the template came from a non-English Excel. Verify the output with ",[14,2361,2345],{}," and oletools in the same job, and prefer moving logic into Python when the macro would only automate what the script could do before saving.",[25,2364,2366],{"id":2365},"frequently-asked-questions","Frequently asked questions",[10,2368,2369,2372],{},[2275,2370,2371],{},"Can xlsxwriter create VBA macros from source code?","\nNo. xlsxwriter embeds a compiled vbaProject.bin that Excel produced. Write the macros in Excel's VBA editor, save a .xlsm file, and extract the binary with vba_extract.py.",[10,2374,2375,2378],{},[2275,2376,2377],{},"Why does Excel say the file format or extension is not valid?","\nThe workbook contains a VBA project but was saved with a .xlsx extension. xlsxwriter does not check the name, so save workbooks with add_vba_project() as .xlsm.",[10,2380,2381,2384],{},[2275,2382,2383],{},"Why does my Workbook_Open macro not run?","\nCheck that macros are enabled for the file, and that the workbook's code name matches the module in the project. A template from a German Excel uses DieseArbeitsmappe; call set_vba_name() with that name.",[10,2386,2387,2390],{},[2275,2388,2389],{},"Can I add a macro to an existing workbook with Python?","\nxlsxwriter only writes new files. To keep macros while editing an existing .xlsm, use openpyxl with keep_vba=True. Adding a new project to an existing file means rewriting it with xlsxwriter or using Excel itself through win32com.",[25,2392,2394],{"id":2393},"related","Related",[2269,2396,2397,2403,2408,2413,2418],{},[2272,2398,2399,2400,2402],{},"Up one level: ",[19,2401,22],{"href":21}," — driving Excel and its macros from Python.",[2272,2404,2405,2407],{},[19,2406,455],{"href":454}," — reading the source back out.",[2272,2409,2410,2412],{},[19,2411,2088],{"href":2087}," — editing cells without losing the project.",[2272,2414,2415,2417],{},[19,2416,2263],{"href":2262}," — running macros instead of shipping them.",[2272,2419,2420,2424],{},[19,2421,2423],{"href":2422},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002F","Building Excel Reports with xlsxwriter"," — the rest of the writer.",[2426,2427,2428],"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 .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}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_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}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}",{"title":35,"searchDepth":334,"depth":334,"links":2430},[2431,2432,2433,2434,2435,2436,2437,2438,2439,2440,2441,2442],{"id":27,"depth":334,"text":28},{"id":259,"depth":334,"text":260},{"id":458,"depth":334,"text":459},{"id":809,"depth":334,"text":810},{"id":1207,"depth":334,"text":1208},{"id":1499,"depth":334,"text":1500},{"id":1911,"depth":334,"text":1912},{"id":2242,"depth":334,"text":2243},{"id":2266,"depth":334,"text":2267},{"id":2332,"depth":334,"text":2333},{"id":2365,"depth":334,"text":2366},{"id":2393,"depth":334,"text":2394},"2026-09-29","Ship a macro-enabled .xlsm report from Python: extract a vbaProject.bin once from a workbook saved in Excel, attach it to every new file with xlsxwriter, wire a button to a macro, keep the workbook and sheet code names consistent, and check the result without opening Excel.","md",[2447,2449,2451,2453],{"q":2371,"a":2448},"No. xlsxwriter embeds a compiled vbaProject.bin that Excel produced. Write the macros in Excel's VBA editor, save a .xlsm file, and extract the binary with vba_extract.py.",{"q":2377,"a":2450},"The workbook contains a VBA project but was saved with a .xlsx extension. xlsxwriter does not check the name, so save workbooks with add_vba_project() as .xlsm.",{"q":2383,"a":2452},"Check that macros are enabled for the file, and that the workbook's code name matches the module in the project. A template from a German Excel uses DieseArbeitsmappe; call set_vba_name() with that name.",{"q":2389,"a":2454},"xlsxwriter only writes new files. To keep macros while editing an existing .xlsm, use openpyxl with keep_vba=True. Adding a new project to an existing file means rewriting it with xlsxwriter or using Excel itself through win32com.","add a vba macro to a workbook with xlsxwriter",null,{"breadcrumb":2458},[2459,2462,2465],{"name":2460,"item":2461},"Home","\u002F",{"name":2463,"item":2464},"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\u002Fadd-a-vba-macro-to-a-workbook-with-xlsxwriter",{"title":2468,"description":2469},"Add a VBA Macro to an Excel File with xlsxwriter (.xlsm)","Add VBA macros to a workbook written by Python with xlsxwriter add_vba_project: get vbaProject.bin with vba_extract, save as .xlsm, insert a button that runs a macro, set code names, and verify the macro is in the file.","add-a-vba-macro-to-a-workbook-with-xlsxwriter","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fadd-a-vba-macro-to-a-workbook-with-xlsxwriter\u002Findex",[2473,220],"macros-and-vba","how-to","SCPQueW2nwrztxFvDDByLfCypeGBhhllK7so-BvD030",[2477,2480],{"title":22,"path":2478,"stem":2479,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Findex",{"title":2481,"path":2482,"stem":2483,"children":-1},"Automate Excel on macOS with Python","\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fautomate-excel-on-macos-with-python","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fautomate-excel-on-macos-with-python\u002Findex",[2485,2486],{"path":2478,"title":22},{"path":2487,"title":2463},"\u002Fgetting-started-with-python-excel-automation",[],[2490,2493,2496,2498,2500,2502],{"path":2491,"title":2492,"kind":2474},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-xlwings-basics\u002Fcall-python-from-excel-with-xlwings-udf","Call Python from Excel with an xlwings UDF",{"path":2494,"title":2495,"kind":2474},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-xlwings-basics\u002Fxlwings-run-macro-from-python-example","xlwings: Run a VBA Macro From Python (Example)",{"path":2497,"title":2250,"kind":2474},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Ftranslate-common-vba-macros-to-python",{"path":2499,"title":455,"kind":2474},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fextract-vba-macro-code-from-an-excel-file",{"path":2501,"title":2263,"kind":2474},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frun-an-excel-macro-from-python-with-win32com",{"path":2503,"title":2504,"kind":2474},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-xlwings-basics\u002Frun-python-from-an-excel-button-with-xlwings","Run Python from an Excel Button with xlwings RunPython",1790972054193]