├── .gitignore ├── demo.xlsm ├── .gitattributes ├── excel_export_code.cmd ├── image └── demo.png ├── LICENSE ├── README.md ├── banner.svg └── excel_export_code.vbs /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | *.xlsm 3 | -------------------------------------------------------------------------------- /demo.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cavo789/vba_excel_export_code/HEAD/demo.xlsm -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /excel_export_code.cmd: -------------------------------------------------------------------------------- 1 | cscript excel_export_code.vbs demo.xlsm //nologo 2 | pause 3 | -------------------------------------------------------------------------------- /image/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cavo789/vba_excel_export_code/HEAD/image/demo.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Christophe Avonture 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Excel - Export code to flat files 2 | 3 | ![banner](./banner.svg) 4 | 5 | > Export every code objects (classes, forms, modules and worksheet code) to flat files, in a batch mode. Export the ribbon manifest and icons too. 6 | 7 | ## Table of Contents 8 | 9 | * [Table of Contents](#table-of-contents) 10 | * [Description](#description) 11 | * [Install](#install) 12 | * [Usage](#usage) 13 | * [See also](#see-also) 14 | * [Author](#author) 15 | * [Contribute](#contribute) 16 | * [License](#license) 17 | 18 | ## Description 19 | 20 | This VB script will export all code objects (classes, forms, modules and worksheet macro) from an Excel file (can be `.xlsm` or `.xlam`) to flat files on your disk. 21 | 22 | This way, you'll get a quick backup of your code and you'll be able to synchronize your code on a versioning platform like GitHub. 23 | 24 | The script will start Excel (hidden way), open the specified file, process every code object and export them, one by one, in a `\src\your_file.xlsm` folder. 25 | 26 | The `src` folder will be automatically created if needed and you'll find a sub-folder having the same name of your file (so you can have more than one exported file in the same src folder). 27 | 28 | If your Excel file also contains a ribbon, the manifest (i.e. the `xml` of the ribbon) and custom icons will also be exported. 29 | 30 | ## Install 31 | 32 | Just get a copy of the `.vbs` script, perhaps the `.cmd` too (for your easiness) and save them in the same folder of your Excel application (containing the code you want to export). 33 | 34 | ## Usage 35 | 36 | Just edit the `.cmd` file and you'll see how it works: you just need to run the `.vbs` with one parameter, the name of your file. 37 | 38 | ![demo](./image/demo.png) 39 | 40 | ## See also 41 | 42 | See also the [https://github.com/cavo789/vbs_xls_import_code](https://github.com/cavo789/vbs_xls_import_code) that do the opposite: import codebase into Excel. 43 | 44 | ## Author 45 | 46 | Christophe Avonture 47 | 48 | ## Contribute 49 | 50 | PRs not accepted. 51 | 52 | ## License 53 | 54 | [MIT](LICENSE) 55 | -------------------------------------------------------------------------------- /banner.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 |
5 | 85 |
86 |

Excel - Export code to flat files

87 |

Export Excel code to flat files

88 |
89 |
90 |
91 |
92 | -------------------------------------------------------------------------------- /excel_export_code.vbs: -------------------------------------------------------------------------------- 1 | ' ======================================================== 2 | ' 3 | ' Author : Christophe Avonture 4 | ' Date : June 2018 5 | ' 6 | ' VBScript that will extract all the VBA code from a workbook 7 | ' (classes, forms, modules and worksheet code) and will export 8 | ' these objects on the filesystem as text file. 9 | ' 10 | ' Export the ribbon (if there is one), manifest and icons. 11 | ' 12 | ' By ussing the script on f.i; the file called c:\repo\cavo.xlam, 13 | ' this script will create the folder c:\repo\src\cavo.xlam\ and, there, 14 | ' one file for each classes, forms, modules and sheets with code. 15 | ' 16 | ' The idea is : make it easy to export the code for a versioning tool 17 | ' like GitHub or other tools 18 | ' 19 | ' Code for the extraction is based on vbaDeveloper 20 | ' https://github.com/hilkoc/vbaDeveloper 21 | ' 22 | ' Changes 23 | ' ======= 24 | ' 25 | ' 2019-04-24 - Automatically export the VBA code of the project but 26 | ' now ask before exporting anything else (like addins) 27 | ' 28 | ' ======================================================== 29 | 30 | Option Explicit 31 | 32 | Const bVerbose = True 33 | 34 | ' Location, if installed, of 7-zip. Will make the export of the 35 | ' ribbon manifest faster 36 | Const ZipProgram = "C:\Program Files\7-Zip\7z.exe" 37 | 38 | Const vbext_ct_StdModule = 1 39 | Const vbext_ct_ClassModule = 2 40 | Const vbext_ct_MSForm = 3 41 | Const vbext_ct_Document = 100 42 | 43 | ' https://ss64.com/vb/popup.html 44 | Const popup_type_YesNo = 4 45 | Const popup_answer_Yes = 6 46 | Const popup_answer_No = 7 47 | 48 | Class clsFiles 49 | 50 | Dim objFSO, objFile 51 | 52 | Private bVerbose 53 | 54 | Public Property Let verbose(bYesNo) 55 | bVerbose = bYesNo 56 | End Property 57 | 58 | Private Sub Class_Initialize() 59 | bVerbose = False 60 | Set objFSO = CreateObject("Scripting.FileSystemObject") 61 | End Sub 62 | 63 | Private Sub Class_Terminate() 64 | Set objFSO = Nothing 65 | End Sub 66 | 67 | Public Function Exists(ByVal sFileName) 68 | Exists = objFSO.FileExists(sFileName) 69 | End Function 70 | 71 | Public Function Delete(ByVal sFileName) 72 | Delete = objFSO.DeleteFile(sFileName) 73 | End Function 74 | 75 | Public Function Rename(ByVal sFileName, ByVal sNewName) 76 | 77 | If (Exists(sNewName)) Then 78 | Delete(sNewName) 79 | End If 80 | 81 | objFSO.MoveFile sFileName, sNewName 82 | 83 | Rename = Exists(sNewName) 84 | 85 | End Function 86 | 87 | ' -------------------------------------------------- 88 | ' Return the file extension (f.i. "accdb") 89 | ' -------------------------------------------------- 90 | Public Function GetExtensionName(ByVal sFileName) 91 | GetExtensionName = objFSO.GetExtensionName(sFileName) 92 | End Function 93 | 94 | ' -------------------------------------------------- 95 | ' Return only the file name (f.i. "db1") 96 | ' -------------------------------------------------- 97 | Public Function GetBaseName(ByVal sFileName) 98 | GetBaseName = objFSO.GetBaseName(sFileName) 99 | End Function 100 | 101 | ' -------------------------------------------------- 102 | ' Return the folder where the file is stored (f.i. c:\temp) 103 | ' -------------------------------------------------- 104 | Public Function GetParentFolderName(ByVal sFileName) 105 | 106 | Dim sPath 107 | Dim objShell 108 | 109 | sPath = "" 110 | 111 | If (Exists(sFileName)) Then 112 | 113 | sPath = objFSO.GetParentFolderName(sFileName) 114 | 115 | ' sPath is empty when sFileName was just a filename 116 | ' like f.i. "workbook.xlsx". So, in that case, get the current 117 | ' folder and concatenate 118 | If (sPath = "") Then 119 | Set objShell = WScript.CreateObject("WScript.Shell") 120 | sPath = objShell.CurrentDirectory 121 | Set objShell = Nothing 122 | End If 123 | 124 | End If 125 | 126 | GetParentFolderName = sPath 127 | 128 | End Function 129 | 130 | End Class 131 | 132 | Class clsFolders 133 | 134 | Dim objFSO, objFile 135 | 136 | Private bVerbose 137 | 138 | Public Property Let verbose(bYesNo) 139 | bVerbose = bYesNo 140 | End Property 141 | 142 | Private Sub Class_Initialize() 143 | bVerbose = False 144 | Set objFSO = CreateObject("Scripting.FileSystemObject") 145 | End Sub 146 | 147 | Private Sub Class_Terminate() 148 | Set objFSO = Nothing 149 | End Sub 150 | 151 | Public Function Exists(sFolderName) 152 | Exists = objFSO.FolderExists(sFolderName) 153 | End Function 154 | 155 | ' -------------------------------------------------- 156 | ' Create a folder recursively 157 | ' -------------------------------------------------- 158 | Public Function Create(ByVal sFolderName) 159 | 160 | Create = false 161 | 162 | If Not exists(sFolderName) Then 163 | 164 | If Create(objFSO.GetParentFolderName(sFolderName)) Then 165 | Create = True 166 | 167 | If bVerbose Then 168 | wScript.echo "Create folder " & sFolderName 169 | End If 170 | 171 | Call objFSO.CreateFolder(sFolderName) 172 | End If 173 | 174 | Else 175 | Create = True 176 | End If 177 | 178 | End Function 179 | 180 | End Class 181 | 182 | Class clsMSExcel 183 | 184 | Private oApplication 185 | Private sFileName 186 | Private bAppHasBeenStarted 187 | Private cFiles 188 | Private cFolders 189 | 190 | Public Property Let FileName(ByVal sName) 191 | sFileName = sName 192 | End Property 193 | 194 | Public Property Get FileName 195 | FileName = sFileName 196 | End Property 197 | 198 | Private Sub Class_Initialize() 199 | bAppHasBeenStarted = False 200 | Set oApplication = Nothing 201 | 202 | Set cFiles = new clsFiles 203 | Set cFolders = new clsFolders 204 | End Sub 205 | 206 | Private Sub Class_Terminate() 207 | Set oApplication = Nothing 208 | Set cFiles = Nothing 209 | Set cFolders = Nothing 210 | End Sub 211 | 212 | ' -------------------------------------------------------- 213 | ' Initialize the oApplication object variable : get a pointer 214 | ' to the current Excel.exe app if already in memory or start 215 | ' a new instance. 216 | ' 217 | ' If a new instance has been started, initialize the variable 218 | ' bAppHasBeenStarted to True so the rest of the script knows 219 | ' that Excel should then be closed by the script. 220 | ' -------------------------------------------------------- 221 | Public Function Instantiate() 222 | 223 | If (oApplication Is Nothing) Then 224 | 225 | On Error Resume Next 226 | 227 | Set oApplication = GetObject(,"Excel.Application") 228 | 229 | If (Err.number <> 0) or (oApplication Is Nothing) Then 230 | Set oApplication = CreateObject("Excel.Application") 231 | ' Remember that Excel has been started by 232 | ' this script ==> should be released 233 | bAppHasBeenStarted = True 234 | End If 235 | 236 | Err.clear 237 | 238 | On Error Goto 0 239 | 240 | End If 241 | 242 | ' Return True if the application was created right 243 | ' now 244 | Instantiate = bAppHasBeenStarted 245 | 246 | End Function 247 | 248 | Public Sub Quit() 249 | 250 | On Error Resume Next 251 | oApplication.Quit 252 | On Error Goto 0 253 | 254 | End Sub 255 | 256 | ' -------------------------------------------------- 257 | ' Detect if the module, class, form has code (Y/N) 258 | ' -------------------------------------------------- 259 | Private Function hasCodeToExport(ByVal Component) 260 | 261 | Dim sFirstLine 262 | 263 | hasCodeToExport = True 264 | 265 | If Component.codeModule.CountOfLines <= 2 Then 266 | sFirstLine = Trim(Component.codeModule.lines(1, 1)) 267 | hasCodeToExport = Not (sFirstLine = "" Or sFirstLine = "Option Explicit") 268 | End If 269 | 270 | End Function 271 | 272 | ' -------------------------------------------------- 273 | ' Export class, module or form to a text file on disk) 274 | ' -------------------------------------------------- 275 | Private Sub exportComponent(ByVal sExportPath, ByVal Component, ByVal sExtension) 276 | 277 | If bVerbose Then 278 | wScript.echo " Export " & Component.name & sExtension 279 | End If 280 | 281 | Component.Export sExportPath & "\" & Component.name & sExtension 282 | 283 | End Sub 284 | 285 | ' -------------------------------------------------- 286 | ' Export sheet to a text file on disk) 287 | ' -------------------------------------------------- 288 | Private Sub exportLines(ByVal sExportPath, ByVal Component) 289 | 290 | Dim sFileName 291 | Dim objFSO, outStream 292 | 293 | Set objFSO = CreateObject("Scripting.FileSystemObject") 294 | 295 | sFileName = sExportPath & "\" & Component.name & ".sheet.cls" 296 | 297 | If bVerbose Then 298 | wScript.echo " Export " & Component.name & ".sheet.cls" 299 | End If 300 | 301 | Set outStream = objFSO.CreateTextFile(sFileName, True, False) 302 | 303 | outStream.Write (Component.codeModule.lines(1, Component.codeModule.CountOfLines)) 304 | outStream.Close 305 | 306 | Set outStream = Nothing 307 | Set objFSO = Nothing 308 | 309 | End Sub 310 | 311 | ' -------------------------------------------------- 312 | ' Export the ribbon and icons (if applicable) 313 | ' 314 | ' Use the built-in unzip feature of Windows and export 315 | ' the "customUI" folder. That folder contains the ribbon 316 | ' -------------------------------------------------- 317 | Private Sub exportRibbonXML(ByVal sExportPath) 318 | 319 | Dim oShell, FilesInZip, file 320 | Dim sFolder, sScript 321 | 322 | ' Initialization 323 | sFolder = sExportPath & "\Ribbon" 324 | 325 | wScript.echo "Prepare exportation of the ribbon" 326 | wScript.echo " Export to " & sFolder 327 | 328 | If cFiles.exists(ZipProgram) Then 329 | 330 | ' Fatest way - Use 7-Zip 331 | 332 | Set oShell = CreateObject("wScript.Shell") 333 | 334 | sScript = chr(34) & ZipProgram & chr(34) & " x -y " & _ 335 | chr(34) & sFilename & chr(34) & " " & _ 336 | "customUI -o" & chr(34) & sFolder & chr(34) 337 | wScript.echo " Use 7-zip (" & sScript & ")" 338 | 339 | oShell.Run sScript 340 | 341 | Else 342 | 343 | ' Use the Zip built-in feature of Windows 344 | Set oShell = CreateObject("Shell.Application") 345 | 346 | ' Open the zip and retrieve the ribbon (if there is one) 347 | 348 | ' oShell.NameSpace requires a .zip file (the extension is really important) 349 | ' So add ".zip" to our file (so f.i. rename to "workbook.xlsx.zip") 350 | cFiles.Rename sFileName, sFileName & ".zip" 351 | 352 | ' Now, we can get the list of files in the "zip" 353 | ' ------------------------------------------------------------------ 354 | ' - THIS LINE IS REALLY REALLY SLOW (CAN TAKE TWO MINUTES OR MORE) - 355 | ' ------------------------------------------------------------------ 356 | Set FilesInZip=oShell.NameSpace(sFileName & ".zip").items 357 | 358 | For each file In FilesInZip 359 | 360 | ' When file name is "customUI", we've found the folder (not the file) 361 | ' with the manifest and icons. Export them 362 | If (file.Name = "customUI") Then 363 | 364 | ' Create the target folder 365 | cFolders.Create(sFolder) 366 | 367 | ' And export the ribbon (i.e. the folder called "customUI") 368 | ' 100 = Display a progression bar 369 | ' 10 = Overwrite if the same file is already found in sFolder 370 | oShell.NameSpace(sFolder).CopyHere(file), &H110 371 | 372 | ' Ok, we can stop, we got the ribbon 373 | Exit For 374 | 375 | End if 376 | Next 377 | 378 | ' Reset the original name 379 | cFiles.Rename sFileName & ".zip", sFileName 380 | 381 | Set FilesInZip = Nothing 382 | 383 | End if 384 | 385 | Set oShell = Nothing 386 | 387 | End Sub 388 | 389 | ' -------------------------------------------------- 390 | ' Export the code of a workbook (can be an addin) 391 | ' -------------------------------------------------- 392 | Public Sub ExportVBACode() 393 | 394 | Dim objFSO, objShell 395 | Dim wb, project 396 | Dim bUpdateLinks, bReadOnly 397 | Dim sProjectFileName, sExportPath, sTemp, sFolder 398 | Dim vbComponent 399 | Dim bContinue 400 | 401 | Set objFSO = CreateObject("Scripting.FileSystemObject") 402 | 403 | If Not (objFSO.FileExists(sFileName)) Then 404 | wScript.echo "Error, the file " & sFileName & " is not found" 405 | Exit sub 406 | End If 407 | 408 | bUpdateLinks = False 409 | bReadOnly = True 410 | 411 | ' Get the parent folder i.e. the folder where the Excel file is stored 412 | sFolder = cFiles.GetParentFolderName(sFileName) 413 | 414 | ' Get the export path 415 | ' When sFileName is f.i. c:\temp\workbook.xlsm, the export path will be 416 | ' c:\temp\src\workbook.xlsm\xxxxx 417 | ' i.e. a subfolder src will be created with a folder for the file and, in that folder 418 | ' every objects (forms, modules, ...) and also the ribbon 419 | 420 | sExportPath = sFolder & "\src\" & cFiles.GetBaseName(sFileName) & "." & _ 421 | cFiles.GetExtensionName(sFileName) 422 | 423 | ' Make the filename absolute; add the parent folder if needed 424 | If (sFileName = cFiles.GetBaseName(sFileName) & "." & cFiles.GetExtensionName(sFileName)) Then 425 | sFileName = cFiles.GetParentFolderName(sFileName) + "\" + sFileName 426 | End If 427 | 428 | ' Open Excel 429 | oApplication.DisplayAlerts = False 430 | oApplication.EnableEvents = False 431 | oApplication.ScreenUpdating = False 432 | 433 | Set wb = oApplication.Workbooks.Open(sFileName, bUpdateLinks, bReadOnly) 434 | 435 | Set objShell = WScript.CreateObject("WScript.Shell") 436 | 437 | If Not (wb is Nothing) Then 438 | 439 | For Each project In oApplication.VBE.VBProjects 440 | 441 | ' In the Excel solution, we can have VBA code for the 442 | ' itself and addins. If the VBProjects has the same name 443 | ' that the file ==> the exportation can be done immediatetly 444 | ' Otherwise the user will be prompted before exporting addins 445 | bContinue = (project.name = cFiles.GetBaseName(sFileName)) 446 | 447 | If (project.Protection = 0) Then 448 | 449 | If Not (bContinue) Then 450 | bContinue = (objShell.Popup ("Export " & project.name & "?",,, popup_type_YesNo) = popup_answer_Yes) 451 | End If 452 | 453 | If bContinue Then 454 | 455 | sTemp = "= Exporting code of " & project.name & " =" 456 | 457 | wScript.echo Replace(Space(Len(sTemp)), " ", "=") & vbCrLF & _ 458 | sTemp & vbCrLf & Replace(Space(Len(sTemp)), " ", "=") & vbCrLf 459 | 460 | sProjectFileName = project.fileName 461 | 462 | If bVerbose Then 463 | wScript.echo "Process " & sProjectFileName 464 | End If 465 | 466 | If (sProjectFileName <> "") Then 467 | ' Extra security : be sure the project has a name, 468 | ' should always be the case 469 | 470 | wScript.echo "Export to " & sExportPath 471 | Call cFolders.Create(sExportPath) 472 | 473 | wScript.echo "" 474 | 475 | For Each vbComponent In project.VBComponents 476 | 477 | If hasCodeToExport(vbComponent) Then 478 | 479 | Select Case vbComponent.Type 480 | Case vbext_ct_ClassModule 481 | exportComponent sExportPath, vbComponent, ".cls" 482 | Case vbext_ct_StdModule 483 | exportComponent sExportPath, vbComponent, ".bas" 484 | Case vbext_ct_MSForm 485 | exportComponent sExportPath, vbComponent, ".frm" 486 | Case vbext_ct_Document 487 | exportLines sExportPath, vbComponent 488 | Case Else 489 | wScript.echo "Unkown vbComponent type " & vbComponent.Name 490 | End Select 491 | 492 | End If 493 | Next 494 | 495 | End If 496 | 497 | End If 498 | 499 | Else 500 | 501 | wScript.echo "ERROR - " & project.name & " is protected with a password, " 502 | wScript.echo "If you want to export code, please first start Excel, open the file, " 503 | wScript.echo "go to the VB editor and type your password so the code become accessible." 504 | wScript.echo "Without exiting Excel, start this script again." 505 | 506 | End if 507 | 508 | wScript.echo "" 509 | 510 | Next 511 | 512 | End if 513 | 514 | Set objShell = Nothing 515 | 516 | On error Resume Next 517 | 518 | ' Fake, let Excel think the file wasn't modified 519 | wb.Saved = True 520 | 521 | ' Don't save changes 522 | wb.Close False 523 | 524 | Set wb = Nothing 525 | 526 | On error GoTo 0 527 | 528 | ' Restore settings 529 | oApplication.DisplayAlerts = True 530 | oApplication.EnableEvents = True 531 | oApplication.ScreenUpdating = True 532 | 533 | ' And now, export the ribbon 534 | ' Note: this is a really slow process; strange... 535 | Call exportRibbonXML(sExportPath) 536 | 537 | End Sub 538 | 539 | End Class 540 | 541 | Sub ShowHelp() 542 | 543 | wScript.echo " ============================" 544 | wScript.echo " = Excel Export Code script =" 545 | wScript.echo " ============================" 546 | wScript.echo "" 547 | wScript.echo " You need to tell which file should be processed " 548 | wScript.echo "" 549 | wScript.echo " For instance :" 550 | wScript.echo "" 551 | wScript.echo " " & Wscript.ScriptName & " myfile.xlam" 552 | wScript.echo "" 553 | wScript.echo " or " 554 | wScript.echo "" 555 | wScript.echo " " & Wscript.ScriptName & " myfile.xlsm" 556 | wScript.echo "" 557 | wScript.quit 558 | 559 | End sub 560 | 561 | ' ----------------------------------------------------- 562 | ' -------------------- ENTRY POINT -------------------- 563 | ' ----------------------------------------------------- 564 | 565 | Dim cMSExcel 566 | Dim sFileName 567 | 568 | ' Get the first argument 569 | If (wScript.Arguments.Count = 0) Then 570 | 571 | Call ShowHelp 572 | 573 | Else 574 | ' Get the file name 575 | sFileName = Trim(Wscript.Arguments.Item(0)) 576 | 577 | Set cMSExcel = New clsMSExcel 578 | 579 | Call cMSExcel.Instantiate 580 | 581 | cMSExcel.FileName = sFileName 582 | 583 | Call cMSExcel.ExportVBACode() 584 | 585 | ' Job done, we can quit Excel 586 | Call cMSExcel.Quit() 587 | 588 | Set cMSExcel = Nothing 589 | 590 | End if 591 | --------------------------------------------------------------------------------