├── .editorconfig
├── .github
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .gitmodules
├── .vscode
└── tasks.json
├── LICENSE.txt
├── README.md
├── build.sh
├── sample
├── Class1.vb
├── Makefile
├── Module1.vb
├── ThisDocument.vb
└── customUI
│ ├── customUI14.xml
│ └── images
│ ├── icons8-about-96.png
│ ├── icons8-contacts-96.png
│ ├── icons8-home-96.png
│ └── license.txt
├── src
├── Directory.Build.props
├── VbaCompiler
│ ├── DirectoryEx.cs
│ ├── Guard.cs
│ ├── Icon.png
│ ├── Internals.cs
│ ├── README.md
│ ├── Vba
│ │ ├── Constants.cs
│ │ ├── DirStream.cs
│ │ ├── HexEncoder.cs
│ │ ├── InformationRecord.cs
│ │ ├── ModuleRecord.cs
│ │ ├── ModuleStream.cs
│ │ ├── ModuleUnit.cs
│ │ ├── ModuleUnitType.cs
│ │ ├── ModulesRecord.cs
│ │ ├── ProjectPassword.cs
│ │ ├── ProjectProtection.cs
│ │ ├── ProjectProtectionState.cs
│ │ ├── ProjectRecord.cs
│ │ ├── ProjectVisibilityState.cs
│ │ ├── ProjectWmRecord.cs
│ │ ├── ReferenceProjectRecord.cs
│ │ ├── ReferenceRecord.cs
│ │ ├── ReferenceRecordType.cs
│ │ ├── ReferenceRegisteredRecord.cs
│ │ ├── ReferencesRecord.cs
│ │ ├── StorageId.cs
│ │ ├── StreamId.cs
│ │ ├── SysKind.cs
│ │ ├── VbaEncodings.cs
│ │ ├── VbaEncryption.cs
│ │ └── VbaProjectStream.cs
│ ├── VbaCompiler.cs
│ └── VbaCompiler.csproj
└── vbamc
│ ├── Icon.png
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ ├── README.md
│ ├── data
│ ├── MacroTemplate.dotx
│ ├── MacroTemplate.potm
│ └── MacroTemplate.xltx
│ └── vbamc.csproj
├── tests
├── VbaCompiler.Benchmark
│ ├── CompileMacroBenchmark.cs
│ ├── CompileVbaProjectBenchmark.cs
│ ├── Program.cs
│ ├── VbaCompiler.Benchmark.csproj
│ └── data
│ │ ├── Class.vb
│ │ ├── MacroTemplate.potm
│ │ └── Module.vb
└── VbaCompiler.Tests
│ ├── GlobalUsings.cs
│ ├── Streams
│ └── VbaCompilerStreamsTests.cs
│ ├── VbaCompiler.Tests.csproj
│ ├── VbaEncryptionTests.cs
│ ├── VbaSamplesNumberGenerator.cs
│ └── data
│ ├── Class.vb
│ └── Module.vb
├── utils
└── vbad
│ ├── DirStream.cs
│ ├── ModuleInfo.cs
│ ├── Program.cs
│ └── vbad.csproj
└── vbamc.sln
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [Makefile]
4 | indent_size = 4
5 | indent_style = tab
6 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | pull_request:
6 | branches: main
7 |
8 | permissions:
9 | contents: read
10 |
11 | env:
12 | DOTNET_NOLOGO: 1
13 | DOTNET_CLI_TELEMETRY_OPTOUT: 1
14 | DOTNET_GENERATE_ASPNET_CERTIFICATE: 0
15 | RestoreLockedMode: true
16 | RepositoryBranch: '${{ github.ref_name }}'
17 | RepositoryCommit: '${{ github.sha }}'
18 |
19 | jobs:
20 | test:
21 | runs-on: ubuntu-22.04
22 |
23 | strategy:
24 | matrix:
25 | Configuration: ['Debug', 'Release']
26 |
27 | env:
28 | Configuration: '${{ matrix.configuration }}'
29 |
30 | steps:
31 | - name: checkout
32 | uses: actions/checkout@v3
33 | with:
34 | submodules: true
35 |
36 | - name: setup dotnet
37 | uses: actions/setup-dotnet@v3
38 | with:
39 | dotnet-version: 8
40 |
41 | - name: restore
42 | run: dotnet restore
43 |
44 | - name: build
45 | run: dotnet build --no-restore
46 |
47 | - name: test
48 | run: dotnet test --no-restore --no-build
49 |
50 | - name: pack
51 | if: ${{ matrix.configuration == 'Release' }}
52 | run: |
53 | dotnet pack src/VbaCompiler/VbaCompiler.csproj --no-build --no-restore -o dist
54 | dotnet pack src/vbamc/vbamc.csproj --no-build --no-restore -o dist
55 |
56 | - name: publish artifact
57 | if: ${{ matrix.configuration == 'Release' }}
58 | uses: actions/upload-artifact@v3
59 | with:
60 | name: 'vbamc_packages_${{ matrix.configuration }}'
61 | path: '${{ github.workspace }}/dist'
62 |
63 | benchmark:
64 | runs-on: ${{ matrix.os }}
65 |
66 | strategy:
67 | matrix:
68 | os: ['ubuntu-22.04', 'windows-2022']
69 |
70 | env:
71 | Configuration: 'Release'
72 | PROJECT: 'tests/VbaCompiler.Benchmark/VbaCompiler.Benchmark.csproj'
73 |
74 | steps:
75 | - name: checkout
76 | uses: actions/checkout@v3
77 | with:
78 | submodules: true
79 |
80 | - name: setup dotnet
81 | uses: actions/setup-dotnet@v3
82 | with:
83 | dotnet-version: 8
84 |
85 | - name: restore
86 | run: dotnet restore
87 |
88 | - name: build
89 | run: dotnet build --no-restore -c Release tests/VbaCompiler.Benchmark/VbaCompiler.Benchmark.csproj
90 |
91 | - name: benchmark
92 | shell: bash
93 | run: |
94 | dotnet run -c Release --project tests/VbaCompiler.Benchmark/VbaCompiler.Benchmark.csproj -- -e github --artifacts
95 |
96 | {
97 | echo "## VBA Compiler Benchmark"
98 | echo ""
99 | echo "Runner: \`${{ matrix.os }}\`"
100 | echo ""
101 | cat "BenchmarkDotNet.Artifacts/results/CompileMacroBenchmark-report-github.md"
102 | } >> $GITHUB_STEP_SUMMARY
103 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | push:
5 | tags: [ 'v*.*.*' ]
6 |
7 | permissions:
8 | contents: read
9 |
10 | env:
11 | DOTNET_NOLOGO: 1
12 | DOTNET_CLI_TELEMETRY_OPTOUT: 1
13 | DOTNET_GENERATE_ASPNET_CERTIFICATE: 0
14 | RestoreLockedMode: true
15 | Configuration: Release
16 | RepositoryBranch: '${{ github.ref_name }}'
17 | RepositoryCommit: '${{ github.sha }}'
18 |
19 | jobs:
20 | build:
21 |
22 | runs-on: windows-2022
23 |
24 | steps:
25 | - name: checkout
26 | uses: actions/checkout@v3
27 | with:
28 | submodules: true
29 |
30 | - name: setup dotnet
31 | uses: actions/setup-dotnet@v3
32 | with:
33 | dotnet-version: 7
34 |
35 | - name: setup AzureSignTool
36 | if: steps.cache-dotnettools.outputs.cache-hit != 'true'
37 | run: dotnet tool install --verbosity minimal --global azuresigntool --version 4.0.1
38 |
39 | - name: setup NuGetKeyVaultSignTool
40 | if: steps.cache-dotnettools.outputs.cache-hit != 'true'
41 | run: dotnet tool install --verbosity minimal --global NuGetKeyVaultSignTool --version 3.2.2
42 |
43 | - name: restore
44 | run: dotnet restore
45 |
46 | - name: build
47 | run: dotnet build --no-restore
48 |
49 | - name: test
50 | run: dotnet test --no-restore --no-build
51 |
52 | - name: sign libraries
53 | id: sign_library
54 | if: ${{ success() && github.event_name == 'push' }}
55 | working-directory: '${{ github.workspace}}'
56 | run: |
57 | AzureSignTool.exe sign `
58 | --file-digest sha256 `
59 | --description-url "https://github.com/NetOfficeFw/vbamc" `
60 | --no-page-hashing `
61 | --timestamp-rfc3161 http://timestamp.digicert.com `
62 | --timestamp-digest sha256 `
63 | --azure-key-vault-url "${{ secrets.KEYVAULT_URL }}" `
64 | --azure-key-vault-tenant-id "${{ secrets.KEYVAULT_TENANT_ID }}" `
65 | --azure-key-vault-client-id "${{ secrets.KEYVAULT_CLIENT_ID }}" `
66 | --azure-key-vault-client-secret "${{ secrets.AZURESIGNTOOL_CLIENT_SECRET }}" `
67 | --azure-key-vault-certificate "goITSolutions-until-2024-01" `
68 | --verbose `
69 | src/VbaCompiler/bin/Release/net6.0/VbaCompiler.dll `
70 | src/VbaCompiler/bin/Release/net7.0/VbaCompiler.dll `
71 | src/vbamc/obj/Release/net6.0/vbamc.dll `
72 | src/vbamc/obj/Release/net7.0/vbamc.dll
73 |
74 | - name: pack
75 | if: ${{ always() }}
76 | run: |
77 | dotnet pack src/VbaCompiler/VbaCompiler.csproj --no-build --no-restore -o dist
78 | dotnet pack src/vbamc/vbamc.csproj --no-build --no-restore -o dist
79 |
80 | - name: sign packages
81 | id: sign_package
82 | if: ${{ steps.sign_library.outcome == 'success' }}
83 | working-directory: '${{ github.workspace}}/dist'
84 | run: |
85 | NuGetKeyVaultSignTool.exe sign *.nupkg `
86 | --file-digest sha256 `
87 | --timestamp-rfc3161 http://timestamp.digicert.com `
88 | --timestamp-digest sha256 `
89 | --azure-key-vault-url "${{ secrets.KEYVAULT_URL }}" `
90 | --azure-key-vault-tenant-id "${{ secrets.KEYVAULT_TENANT_ID }}" `
91 | --azure-key-vault-client-id "${{ secrets.KEYVAULT_CLIENT_ID }}" `
92 | --azure-key-vault-client-secret "${{ secrets.AZURESIGNTOOL_CLIENT_SECRET }}" `
93 | --azure-key-vault-certificate "goITSolutions-until-2024-01"
94 |
95 | - name: publish packages
96 | if: ${{ steps.sign_package.outcome == 'success' }}
97 | working-directory: '${{ github.workspace}}/dist'
98 | run: |
99 | dotnet nuget push "*.nupkg" --api-key $env:NUGET_PUSH_KEY --source https://api.nuget.org/v3/index.json
100 | env:
101 | NUGET_PUSH_KEY: ${{ secrets.NUGET_PUSH_KEY }}
102 |
103 | - name: publish artifact
104 | uses: actions/upload-artifact@v3
105 | with:
106 | name: vbamc_build_${{ github.run_id }}_preview${{ github.run_number }}
107 | path: '${{ github.workspace }}/dist'
108 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### DotnetCore ###
2 | # .NET Core build folders
3 | bin/
4 | obj/
5 | dist/
6 |
7 | # Common node modules locations
8 | /node_modules
9 | /wwwroot/node_modules
10 |
11 | ### VisualStudioCode ###
12 | .vscode/*
13 | !.vscode/settings.json
14 | !.vscode/tasks.json
15 | !.vscode/launch.json
16 | !.vscode/extensions.json
17 | !.vscode/*.code-snippets
18 |
19 | # Local History for Visual Studio Code
20 | .history/
21 |
22 | # Built Visual Studio Code Extensions
23 | *.vsix
24 |
25 | ### VisualStudioCode Patch ###
26 | # Ignore all local history of files
27 | .history
28 | .ionide
29 |
30 | # Support for Project snippet scope
31 |
32 | ### VisualStudio ###
33 | ## Ignore Visual Studio temporary files, build results, and
34 | ## files generated by popular Visual Studio add-ons.
35 | ##
36 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
37 |
38 | # User-specific files
39 | *.rsuser
40 | *.suo
41 | *.user
42 | *.userosscache
43 | *.sln.docstates
44 |
45 | # User-specific files (MonoDevelop/Xamarin Studio)
46 | *.userprefs
47 |
48 | # Mono auto generated files
49 | mono_crash.*
50 |
51 | # Build results
52 | [Dd]ebug/
53 | [Dd]ebugPublic/
54 | [Rr]elease/
55 | [Rr]eleases/
56 | x64/
57 | x86/
58 | [Ww][Ii][Nn]32/
59 | [Aa][Rr][Mm]/
60 | [Aa][Rr][Mm]64/
61 | bld/
62 | [Bb]in/
63 | [Oo]bj/
64 | [Ll]og/
65 | [Ll]ogs/
66 |
67 | # Visual Studio 2015/2017 cache/options directory
68 | .vs/
69 | # Uncomment if you have tasks that create the project's static files in wwwroot
70 | #wwwroot/
71 |
72 | # Visual Studio 2017 auto generated files
73 | Generated\ Files/
74 |
75 | # MSTest test Results
76 | [Tt]est[Rr]esult*/
77 | [Bb]uild[Ll]og.*
78 |
79 | # NUnit
80 | *.VisualState.xml
81 | TestResult.xml
82 | nunit-*.xml
83 |
84 | # Build Results of an ATL Project
85 | [Dd]ebugPS/
86 | [Rr]eleasePS/
87 | dlldata.c
88 |
89 | # Benchmark Results
90 | BenchmarkDotNet.Artifacts/
91 |
92 | # .NET Core
93 | project.lock.json
94 | project.fragment.lock.json
95 | artifacts/
96 |
97 | # ASP.NET Scaffolding
98 | ScaffoldingReadMe.txt
99 |
100 | # StyleCop
101 | StyleCopReport.xml
102 |
103 | # Files built by Visual Studio
104 | *_i.c
105 | *_p.c
106 | *_h.h
107 | *.ilk
108 | *.meta
109 | *.obj
110 | *.iobj
111 | *.pch
112 | *.pdb
113 | *.ipdb
114 | *.pgc
115 | *.pgd
116 | *.rsp
117 | *.sbr
118 | *.tlb
119 | *.tli
120 | *.tlh
121 | *.tmp
122 | *.tmp_proj
123 | *_wpftmp.csproj
124 | *.log
125 | *.tlog
126 | *.vspscc
127 | *.vssscc
128 | .builds
129 | *.pidb
130 | *.svclog
131 | *.scc
132 |
133 | # Chutzpah Test files
134 | _Chutzpah*
135 |
136 | # Visual C++ cache files
137 | ipch/
138 | *.aps
139 | *.ncb
140 | *.opendb
141 | *.opensdf
142 | *.sdf
143 | *.cachefile
144 | *.VC.db
145 | *.VC.VC.opendb
146 |
147 | # Visual Studio profiler
148 | *.psess
149 | *.vsp
150 | *.vspx
151 | *.sap
152 |
153 | # Visual Studio Trace Files
154 | *.e2e
155 |
156 | # TFS 2012 Local Workspace
157 | $tf/
158 |
159 | # Guidance Automation Toolkit
160 | *.gpState
161 |
162 | # ReSharper is a .NET coding add-in
163 | _ReSharper*/
164 | *.[Rr]e[Ss]harper
165 | *.DotSettings.user
166 |
167 | # TeamCity is a build add-in
168 | _TeamCity*
169 |
170 | # DotCover is a Code Coverage Tool
171 | *.dotCover
172 |
173 | # AxoCover is a Code Coverage Tool
174 | .axoCover/*
175 | !.axoCover/settings.json
176 |
177 | # Coverlet is a free, cross platform Code Coverage Tool
178 | coverage*.json
179 | coverage*.xml
180 | coverage*.info
181 |
182 | # Visual Studio code coverage results
183 | *.coverage
184 | *.coveragexml
185 |
186 | # NCrunch
187 | _NCrunch_*
188 | .*crunch*.local.xml
189 | nCrunchTemp_*
190 |
191 | # MightyMoose
192 | *.mm.*
193 | AutoTest.Net/
194 |
195 | # Web workbench (sass)
196 | .sass-cache/
197 |
198 | # Installshield output folder
199 | [Ee]xpress/
200 |
201 | # DocProject is a documentation generator add-in
202 | DocProject/buildhelp/
203 | DocProject/Help/*.HxT
204 | DocProject/Help/*.HxC
205 | DocProject/Help/*.hhc
206 | DocProject/Help/*.hhk
207 | DocProject/Help/*.hhp
208 | DocProject/Help/Html2
209 | DocProject/Help/html
210 |
211 | # Click-Once directory
212 | publish/
213 |
214 | # Publish Web Output
215 | *.[Pp]ublish.xml
216 | *.azurePubxml
217 | # Note: Comment the next line if you want to checkin your web deploy settings,
218 | # but database connection strings (with potential passwords) will be unencrypted
219 | *.pubxml
220 | *.publishproj
221 |
222 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
223 | # checkin your Azure Web App publish settings, but sensitive information contained
224 | # in these scripts will be unencrypted
225 | PublishScripts/
226 |
227 | # NuGet Packages
228 | *.nupkg
229 | # NuGet Symbol Packages
230 | *.snupkg
231 | # The packages folder can be ignored because of Package Restore
232 | **/[Pp]ackages/*
233 | # except build/, which is used as an MSBuild target.
234 | !**/[Pp]ackages/build/
235 | # Uncomment if necessary however generally it will be regenerated when needed
236 | #!**/[Pp]ackages/repositories.config
237 | # NuGet v3's project.json files produces more ignorable files
238 | *.nuget.props
239 | *.nuget.targets
240 |
241 | # Microsoft Azure Build Output
242 | csx/
243 | *.build.csdef
244 |
245 | # Microsoft Azure Emulator
246 | ecf/
247 | rcf/
248 |
249 | # Windows Store app package directories and files
250 | AppPackages/
251 | BundleArtifacts/
252 | Package.StoreAssociation.xml
253 | _pkginfo.txt
254 | *.appx
255 | *.appxbundle
256 | *.appxupload
257 |
258 | # Visual Studio cache files
259 | # files ending in .cache can be ignored
260 | *.[Cc]ache
261 | # but keep track of directories ending in .cache
262 | !?*.[Cc]ache/
263 |
264 | # Others
265 | ClientBin/
266 | ~$*
267 | *~
268 | *.dbmdl
269 | *.dbproj.schemaview
270 | *.jfm
271 | *.pfx
272 | *.publishsettings
273 | orleans.codegen.cs
274 |
275 | # Including strong name files can present a security risk
276 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
277 | #*.snk
278 |
279 | # Since there are multiple workflows, uncomment next line to ignore bower_components
280 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
281 | #bower_components/
282 |
283 | # RIA/Silverlight projects
284 | Generated_Code/
285 |
286 | # Backup & report files from converting an old project file
287 | # to a newer Visual Studio version. Backup files are not needed,
288 | # because we have git ;-)
289 | _UpgradeReport_Files/
290 | Backup*/
291 | UpgradeLog*.XML
292 | UpgradeLog*.htm
293 | ServiceFabricBackup/
294 | *.rptproj.bak
295 |
296 | # SQL Server files
297 | *.mdf
298 | *.ldf
299 | *.ndf
300 |
301 | # Business Intelligence projects
302 | *.rdl.data
303 | *.bim.layout
304 | *.bim_*.settings
305 | *.rptproj.rsuser
306 | *- [Bb]ackup.rdl
307 | *- [Bb]ackup ([0-9]).rdl
308 | *- [Bb]ackup ([0-9][0-9]).rdl
309 |
310 | # Microsoft Fakes
311 | FakesAssemblies/
312 |
313 | # GhostDoc plugin setting file
314 | *.GhostDoc.xml
315 |
316 | # Node.js Tools for Visual Studio
317 | .ntvs_analysis.dat
318 | node_modules/
319 |
320 | # Visual Studio 6 build log
321 | *.plg
322 |
323 | # Visual Studio 6 workspace options file
324 | *.opt
325 |
326 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
327 | *.vbw
328 |
329 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
330 | *.vbp
331 |
332 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
333 | *.dsw
334 | *.dsp
335 |
336 | # Visual Studio 6 technical files
337 |
338 | # Visual Studio LightSwitch build output
339 | **/*.HTMLClient/GeneratedArtifacts
340 | **/*.DesktopClient/GeneratedArtifacts
341 | **/*.DesktopClient/ModelManifest.xml
342 | **/*.Server/GeneratedArtifacts
343 | **/*.Server/ModelManifest.xml
344 | _Pvt_Extensions
345 |
346 | # Paket dependency manager
347 | .paket/paket.exe
348 | paket-files/
349 |
350 | # FAKE - F# Make
351 | .fake/
352 |
353 | # CodeRush personal settings
354 | .cr/personal
355 |
356 | # Python Tools for Visual Studio (PTVS)
357 | __pycache__/
358 | *.pyc
359 |
360 | # Cake - Uncomment if you are using it
361 | # tools/**
362 | # !tools/packages.config
363 |
364 | # Tabs Studio
365 | *.tss
366 |
367 | # Telerik's JustMock configuration file
368 | *.jmconfig
369 |
370 | # BizTalk build output
371 | *.btp.cs
372 | *.btm.cs
373 | *.odx.cs
374 | *.xsd.cs
375 |
376 | # OpenCover UI analysis results
377 | OpenCover/
378 |
379 | # Azure Stream Analytics local run output
380 | ASALocalRun/
381 |
382 | # MSBuild Binary and Structured Log
383 | *.binlog
384 |
385 | # NVidia Nsight GPU debugger configuration file
386 | *.nvuser
387 |
388 | # MFractors (Xamarin productivity tool) working folder
389 | .mfractor/
390 |
391 | # Local History for Visual Studio
392 | .localhistory/
393 |
394 | # Visual Studio History (VSHistory) files
395 | .vshistory/
396 |
397 | # BeatPulse healthcheck temp database
398 | healthchecksdb
399 |
400 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
401 | MigrationBackup/
402 |
403 | # Ionide (cross platform F# VS Code tools) working folder
404 | .ionide/
405 |
406 | # Fody - auto-generated XML schema
407 | FodyWeavers.xsd
408 |
409 | # VS Code files for those working on multiple tools
410 | *.code-workspace
411 |
412 | # Local History for Visual Studio Code
413 |
414 | # Windows Installer files from build outputs
415 | *.cab
416 | *.msi
417 | *.msix
418 | *.msm
419 | *.msp
420 |
421 | ### macOS ###
422 | # General
423 | .DS_Store
424 | .AppleDouble
425 | .LSOverride
426 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/VbaCompression"]
2 | path = lib/VbaCompression
3 | url = https://github.com/NetOfficeFw/VbaCompression
4 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/src/vbamc/vbamc.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile",
15 | "presentation": {
16 | "reveal": "silent",
17 | }
18 | },
19 | {
20 | "label": "watch",
21 | "command": "dotnet",
22 | "type": "process",
23 | "args": [
24 | "watch",
25 | "run",
26 | "--project",
27 | "${workspaceFolder}/src/vbamc/vbamc.csproj"
28 | ],
29 | "problemMatcher": "$msCompile"
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2023 Jozef Izso
4 | © 2023 Cisco Systems, Inc. All rights reserved.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vbamc
2 |
3 | > Compile macro enabled add-ins for Microsoft Office applications.
4 |
5 | Compiler `vbamc` compiles Visual Basic source code and Ribbon customizations to Microsoft Office macro files or macro enabled add-ins. It supports Microsoft Word, Excel and PowerPoint.
6 |
7 |
8 | ## Installation
9 |
10 | Use `dotnet tool` command to install the `vbamc` compiler:
11 |
12 | ```commandline
13 | dotnet tool install --global vbamc
14 | ```
15 |
16 |
17 | ## Usage
18 |
19 | Pass the list of source code files with modules and classes to the compiler:
20 |
21 | ```commandline
22 | vbamc -m Module.vb -c MyClass.vb -f AcmeSample.ppam -n "Sample Addin" --company "ACME"
23 | ```
24 |
25 | It will generate the PowerPoint Add-in macro file named `AcmeSampleMacro.ppam` usable
26 | in Microsoft PowerPoint.
27 |
28 |
29 | ## When to use macros and why
30 |
31 | There are several principal reasons to consider using Visual Basic
32 | for Applications (VBA) macros in Microsoft Office.
33 |
34 |
35 | ### Automation and repetition
36 |
37 | VBA is effective and efficient when it comes to repetitive solutions to formatting or correction problems. For example, have you ever changed the style of the paragraph at the top of each page in Word? Have you ever had to reformat multiple tables that were pasted from Excel into a Word document or an Outlook email? Have you ever had to make the same change in multiple Outlook contacts?
38 |
39 | If you have a change that you have to make more than ten or twenty times, it may be worth automating it with VBA. If it is a change that you have to do hundreds of times, it certainly is worth considering. Almost any formatting or editing change that you can do by hand, can be done in VBA.
40 |
41 | ### Extensions to user interaction
42 |
43 | There are times when you want to encourage or compel users to interact with the Office application or document in a particular way that is not part of the standard application. For example, you might want to prompt users to take some particular action when they open, save, or print a document.
44 |
45 | ### Interaction between Office applications
46 |
47 | Do you need to copy all of your contacts from Outlook to Word and then format them in some particular way? Or, do you need to move data from Excel to a set of PowerPoint slides? Sometimes simple copy and paste does not do what you want it to do, or it is too slow. Use VBA programming to interact with the details of two or more Office applications at the same time and then modify the content in one application based on the content in another.
48 |
49 | ### Doing things another way
50 |
51 | VBA programming is a powerful solution, but it is not always the optimal approach. Sometimes it makes sense to use other ways to achieve your aims.
52 |
53 | The critical question to ask is whether there is an easier way. Before you begin a VBA project, consider the built-in tools and standard functionalities. For example, if you have a time-consuming editing or layout task, consider using styles or accelerator keys to solve the problem. Can you perform the task once and then use CTRL+Y (Redo) to repeat it? Can you create a new document with the correct format or template, and then copy the content into that new document?
54 |
55 | Office applications are powerful; the solution that you need may already be there. Take some time to learn more about Office before you jump into programming.
56 |
57 | Before you begin a VBA project, ensure that you have the time to work with VBA. Programming requires focus and can be unpredictable. Especially as a beginner, never turn to programming unless you have time to work carefully. Trying to write a "quick script" to solve a problem when a deadline looms can result in a very stressful situation. If you are in a rush, you might want to use conventional methods, even if they are monotonous and repetitive.
58 |
59 |
60 | > Source: [Getting started with VBA in Office](https://learn.microsoft.com/en-us/office/vba/library-reference/concepts/getting-started-with-vba-in-office), under [CC-BY-4.0](https://github.com/MicrosoftDocs/VBA-Docs/blob/main/LICENSE) license.
61 |
62 |
63 | ## License
64 |
65 | Source code is licensed under [MIT License](LICENSE.txt).
66 |
67 | Copyright © 2023 Jozef Izso
68 | © 2023 Cisco Systems, Inc. All rights reserved.
69 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | dotnet publish -c Release -r osx.10.15-x64 -f net6.0 --self-contained true -o dist
4 |
--------------------------------------------------------------------------------
/sample/Class1.vb:
--------------------------------------------------------------------------------
1 | Public Sub MainMethod()
2 | ' call me maybe
3 | End Sub
4 |
--------------------------------------------------------------------------------
/sample/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Cisco Systems, Inc.
2 | # Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | framework := net7.0
5 |
6 | .PHONY: all
7 | all: addin
8 |
9 | .PHONY: addin
10 | addin: bin/PresentationAddin.ppam bin/PresentationAddin.pptm
11 |
12 | MODULES = Module1.vb
13 | CLASSES = Class1.vb
14 |
15 | bin/PresentationAddin.ppam: $(MODULES) $(CLASSES)
16 | ../src/vbamc/bin/Debug/$(framework)/vbamc -m $(MODULES) -c $(CLASSES) -f "PresentationAddin.ppam" -n "Sample Addin" --company "ACME" -p Generator=VbaCompiler
17 |
18 | bin/PresentationAddin.pptm: $(MODULES) $(CLASSES)
19 | ../src/vbamc/bin/Debug/$(framework)/vbamc -m $(MODULES) -c $(CLASSES) -f "PresentationAddin.pptm" -n "Sample Addin" --company "ACME" -p Generator=VbaCompiler
20 |
21 | .PHONY: clean
22 | clean:
23 | rm -rf bin/ obj/
24 |
--------------------------------------------------------------------------------
/sample/Module1.vb:
--------------------------------------------------------------------------------
1 | ' Module source code
2 | Declare PtrSafe Function GetCurrentProcessId Lib "kernel32" () As Long
3 |
4 | Public Sub Auto_Open()
5 | ' execute code when application starts
6 | ' MsgBox "Hello world from " & Application.Name & ", PID " & CStr(GetCurrentProcessId)
7 | End Sub
8 |
9 | Public Sub OnActionAbout(control As IRibbonControl)
10 | MsgBox "User profile: ~/"
11 | MsgBox "Hello world from " & Application.Name & ", PID " & CStr(GetCurrentProcessId)
12 | End Sub
13 |
--------------------------------------------------------------------------------
/sample/ThisDocument.vb:
--------------------------------------------------------------------------------
1 | ' ThisDocument file
2 |
3 | Public Sub ShowMessage()
4 | MsgBox "Hello world from " & Application.Name & ", PID " & CStr(GetCurrentProcessId)
5 | End Sub
6 |
--------------------------------------------------------------------------------
/sample/customUI/customUI14.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/sample/customUI/images/icons8-about-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/sample/customUI/images/icons8-about-96.png
--------------------------------------------------------------------------------
/sample/customUI/images/icons8-contacts-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/sample/customUI/images/icons8-contacts-96.png
--------------------------------------------------------------------------------
/sample/customUI/images/icons8-home-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/sample/customUI/images/icons8-home-96.png
--------------------------------------------------------------------------------
/sample/customUI/images/license.txt:
--------------------------------------------------------------------------------
1 | Universal Multimedia Licensing Agreement for Icons8
2 |
3 | Images are licensed from Icons8 service.
4 |
5 | See https://icons8.com/license for more information
6 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;net7.0;net8.0
5 | enable
6 | enable
7 | 1.8.0
8 |
9 |
10 |
11 | NetOfficeFw
12 | Jozef Izso
13 | Copyright © 2023 Jozef Izso, © 2023 Cisco Systems, Inc. All rights reserved.
14 | MIT
15 | https://github.com/NetOfficeFw/vbamc
16 | README.md
17 | Icon.png
18 |
19 |
20 |
21 |
22 | true
23 | true
24 | true
25 | snupkg
26 | true
27 |
28 |
29 |
30 |
31 | all
32 | runtime; build; native; contentfiles; analyzers; buildtransitive
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/VbaCompiler/DirectoryEx.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc
7 | {
8 | public static class DirectoryEx
9 | {
10 | public static void EnsureDirectory(string path)
11 | {
12 | if (!Directory.Exists(path))
13 | {
14 | Directory.CreateDirectory(path);
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Guard.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 | using vbamc.Vba;
6 |
7 | namespace vbamc
8 | {
9 | public class Guard
10 | {
11 | public static void EnsureLengthBetween(byte[] value, int min, int max, string paramName, string message)
12 | {
13 | if (!(value.Length >= min && value.Length <= max))
14 | {
15 | throw new ArgumentOutOfRangeException(paramName, message);
16 | }
17 | }
18 |
19 | public static void EnsureNoNullCharacters(byte[] value, string paramName, string message)
20 | {
21 | if (value.Any(b => b == VbaEncodings.NULL))
22 | {
23 | throw new ArgumentOutOfRangeException(paramName, message);
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/src/VbaCompiler/Icon.png
--------------------------------------------------------------------------------
/src/VbaCompiler/Internals.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System.Runtime.CompilerServices;
5 |
6 | [assembly: InternalsVisibleTo("VbaCompiler.Tests")]
7 |
--------------------------------------------------------------------------------
/src/VbaCompiler/README.md:
--------------------------------------------------------------------------------
1 | Library `VbaCompiler` compiles Visual Basic source code and Ribbon customizations to Microsoft Office macro enabled files.
2 |
3 | ### Usage
4 |
5 | Sample usage to generate Excel workbook file with macro
6 | from the source code files `Module.vb` and `MyClass.vb`:
7 |
8 | ```csharp
9 | using DocumentFormat.OpenXml.Packaging;
10 |
11 | var compiler = new VbaCompiler();
12 | compiler.ProjectId = Guid.NewGuid();
13 | compiler.ProjectName = "My Macro Project";
14 | compiler.CompanyName = "ACME";
15 | compiler.AddModule("Module.vb");
16 | compiler.AddClass("MyClass.vb");
17 |
18 | // generate the vbaProject.bin file
19 | var vbaProjectPath = compiler.CompileVbaProject("obj", "vbaProject.bin");
20 |
21 | // generate Excel macro file
22 | var macroFilePath = compiler.CompileExcelMacroFile("bin", "MyMacro.xlsm", vbaProjectPath, SpreadsheetDocumentType.MacroEnabledWorkbook);
23 | ```
24 |
25 | ### Requirements
26 |
27 | The compiler works on .NET 6, 7 and 8 runtimes on Windows and macOS.
28 |
29 | ### Samples
30 |
31 | Discover samples in our repository at
32 |
33 |
34 | _Project icon [Code][1] is licensed from Icons8 service under Universal Multimedia Licensing Agreement for Icons8._
35 | _See for more information_
36 |
37 | [1]: https://icons8.com/icon/43988/code
38 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/Constants.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public static class Constants
9 | {
10 | public static readonly string VersionCompatible32 = "393222000";
11 |
12 | public static readonly string HostExtender_VBE = "&H00000001={3832D640-CF90-11CF-8E43-00A0C911005A};VBE;&H00000000";
13 |
14 | public static readonly Version VersionOffice2003 = new Version(0x645E9423, 6);
15 | public static readonly Version VersionOffice365 = new Version(0x645BE109, 11);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/DirStream.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public class DirStream
9 | {
10 | public const ushort TerminatorValue = 0x0010;
11 | public const uint ReservedValue = 0;
12 |
13 | public byte[] GetData(ProjectRecord project)
14 | {
15 | var information = new InformationRecord();
16 | information.SysKind = SysKind.Win32;
17 | information.ProjectName = project.Name;
18 | information.ProjectVersion = Constants.VersionOffice365;
19 |
20 | var references = new ReferencesRecord();
21 | var modules = new ModulesRecord(project.Modules);
22 |
23 | var memory = new MemoryStream(1024);
24 | var writer = new BinaryWriter(memory);
25 |
26 | // Records
27 | information.WriteTo(writer);
28 | references.WriteTo(writer);
29 | modules.WriteTo(writer);
30 |
31 | // Terminator
32 | writer.Write(TerminatorValue);
33 | writer.Write(ReservedValue);
34 |
35 | writer.Close();
36 |
37 | return memory.ToArray();
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/HexEncoder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | // Copyright 2021 Ryan Crosby
9 | // This code is licensed under the MIT license
10 | // Source: https://gist.github.com/crozone/06c4aa41e13be89def1352ba0d378b0f
11 | public class HexEncoder
12 | {
13 | public static void HexEncodeBytes(ReadOnlySpan inputBytes, Span hexEncodedChars)
14 | {
15 | Span hexAlphabet = stackalloc char[] {
16 | '0',
17 | '1',
18 | '2',
19 | '3',
20 | '4',
21 | '5',
22 | '6',
23 | '7',
24 | '8',
25 | '9',
26 | 'A',
27 | 'B',
28 | 'C',
29 | 'D',
30 | 'E',
31 | 'F'
32 | };
33 |
34 | for (int i = 0; i < inputBytes.Length; i++)
35 | {
36 | hexEncodedChars[i * 2] = hexAlphabet[inputBytes[i] >> 4];
37 | hexEncodedChars[i * 2 + 1] = hexAlphabet[inputBytes[i] & 0xF];
38 | }
39 | }
40 |
41 | public static string BytesToHexString(ReadOnlySpan inputBytes)
42 | {
43 | int finalLength = inputBytes.Length * 2;
44 | Span encodedChars = finalLength < 2048 ? stackalloc char[finalLength] : new char[finalLength];
45 | HexEncodeBytes(inputBytes, encodedChars);
46 | return new string(encodedChars);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/InformationRecord.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public class InformationRecord
9 | {
10 | public const short SysKindId = 0x0001;
11 | public const short CompatVersionId = 0x004A;
12 | public const short LcidId = 0x0002;
13 | public const short LcidInvokeId = 0x0014;
14 | public const short CodePageId = 0x0003;
15 | public const short NameId = 0x0004;
16 | public const short DocStringId = 0x0005;
17 | public const short DocStringReserved = 0x0040;
18 | public const short HelpFilePathId = 0x0006;
19 | public const short HelpFilePathReserved = 0x003D;
20 | public const short HelpContextId = 0x0007;
21 | public const short LibFlagsId = 0x0008;
22 | public const short VersionId = 0x0009;
23 | public const short ConstantsId = 0x000C;
24 | public const short ConstantsReserved = 0x003C;
25 |
26 | public const int LcidValue = 0x0409;
27 |
28 | public SysKind SysKind { get; set; }
29 |
30 | public string? ProjectName { get; set; }
31 |
32 | public string? ProjectDescription { get; set; }
33 |
34 | public int CompatVersion => 2;
35 |
36 | public int Lcid => LcidValue;
37 |
38 | public int LcidInvoke => LcidValue;
39 |
40 | public int CodePage => VbaEncodings.Default.CodePage;
41 |
42 | public string ProjectHelpFilePath => "";
43 |
44 | public int ProjectHelpContext => 0;
45 |
46 | ///
47 | /// ProjectLibFlags MUST have value 0x00000000.
48 | ///
49 | public int ProjectLibFlags => 0;
50 |
51 | public Version? ProjectVersion { get; set; }
52 |
53 | public string? ProjectConstants { get; set; }
54 |
55 |
56 | public void WriteTo(BinaryWriter writer)
57 | {
58 | if (this.ProjectVersion == null)
59 | {
60 | this.ProjectVersion = Constants.VersionOffice365;
61 | }
62 |
63 | this.ProjectName = this.ProjectName ?? "";
64 | this.ProjectDescription = this.ProjectDescription ?? "";
65 | this.ProjectConstants = this.ProjectConstants ?? "";
66 |
67 | // PROJECTSYSKIND record
68 | writer.Write(SysKindId);
69 | writer.Write(4);
70 | writer.Write((int)this.SysKind);
71 |
72 | // PROJECTCOMPATVERSION record
73 | writer.Write(CompatVersionId);
74 | writer.Write(4);
75 | writer.Write(this.CompatVersion);
76 |
77 | // PROJECTLCID record
78 | writer.Write(LcidId);
79 | writer.Write(4);
80 | writer.Write(this.Lcid);
81 |
82 | // PROJECTLCIDINVOKE record
83 | writer.Write(LcidInvokeId);
84 | writer.Write(4);
85 | writer.Write(this.LcidInvoke);
86 |
87 | // PROJECTCODEPAGE record
88 | writer.Write(CodePageId);
89 | writer.Write(2);
90 | writer.Write((short)this.CodePage);
91 |
92 | // PROJECTNAME record
93 | var nameBytes = VbaEncodings.Default.GetBytes(this.ProjectName);
94 | Guard.EnsureLengthBetween(nameBytes, 1, 128, nameof(ProjectName), "Project name length must be between 1 and 128 bytes long.");
95 |
96 | writer.Write(NameId);
97 | writer.Write(nameBytes.Length);
98 | writer.Write(nameBytes);
99 |
100 | // PROJECTDOCSTRING record
101 | var docStringBytes = VbaEncodings.Default.GetBytes(this.ProjectDescription);
102 | var docStringUnicodeBytes = VbaEncodings.UTF16.GetBytes(this.ProjectDescription);
103 |
104 | Guard.EnsureLengthBetween(docStringBytes, 0, 2000, nameof(ProjectDescription), "Project description length must be up to 2000 bytes long.");
105 | Guard.EnsureNoNullCharacters(docStringBytes, nameof(ProjectDescription), "Project description cannot contain null characters.");
106 |
107 | writer.Write(DocStringId);
108 | writer.Write(docStringBytes.Length);
109 | writer.Write(docStringBytes);
110 | writer.Write(DocStringReserved);
111 | writer.Write(docStringUnicodeBytes.Length);
112 | writer.Write(docStringUnicodeBytes);
113 |
114 | // PROJECTHELPFILEPATH record
115 | var helpFilePathBytes = VbaEncodings.Default.GetBytes(this.ProjectHelpFilePath);
116 | var helpFilePathUnicodeBytes = VbaEncodings.UTF16.GetBytes(this.ProjectHelpFilePath);
117 |
118 | Guard.EnsureLengthBetween(helpFilePathBytes, 0, 260, nameof(ProjectHelpFilePath), "Project help file path length must be up to 260 bytes long.");
119 | Guard.EnsureNoNullCharacters(helpFilePathBytes, nameof(ProjectHelpFilePath), "Project help file path cannot contain null characters.");
120 |
121 | writer.Write(HelpFilePathId);
122 | writer.Write(helpFilePathBytes.Length);
123 | writer.Write(HelpFilePathReserved);
124 | writer.Write(helpFilePathUnicodeBytes.Length);
125 | writer.Write(helpFilePathUnicodeBytes);
126 |
127 | // PROJECTHELPCONTEXT record
128 | writer.Write(HelpContextId);
129 | writer.Write(4);
130 | writer.Write(this.ProjectHelpContext);
131 |
132 | // PROJECTLIBFLAGS record
133 | writer.Write(LibFlagsId);
134 | writer.Write(4);
135 | writer.Write(this.ProjectLibFlags);
136 |
137 | // PROJECTVERSION record
138 | writer.Write(VersionId);
139 | writer.Write(4);
140 | writer.Write((int)this.ProjectVersion.Major);
141 | writer.Write((short)this.ProjectVersion.Minor);
142 |
143 | // PROJECTCONSTANTS record
144 | var constantsBytes = VbaEncodings.Default.GetBytes(this.ProjectConstants);
145 | var constantsUnicodeBytes = VbaEncodings.UTF16.GetBytes(this.ProjectConstants);
146 |
147 | Guard.EnsureLengthBetween(constantsBytes, 0, 1015, nameof(ProjectConstants), "Project constants length must be up to 1015 bytes long.");
148 | Guard.EnsureNoNullCharacters(constantsBytes, nameof(ProjectConstants), "Project constants cannot contain null characters.");
149 |
150 | writer.Write(ConstantsId);
151 | writer.Write(constantsBytes.Length);
152 | writer.Write(ConstantsReserved);
153 | writer.Write(constantsUnicodeBytes.Length);
154 | writer.Write(constantsUnicodeBytes);
155 |
156 | // PROJECTREFERENCES record
157 | // TODO: implement...
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ModuleRecord.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public class ModuleRecord
9 | {
10 | public const short NameId = 0x0019;
11 | public const short NameUnicodeId = 0x0047;
12 | public const short StreamNameId = 0x001A;
13 | public const short StreamNameReserved = 0x0032;
14 | public const short DocStringId = 0x001C;
15 | public const short DocStringReserved = 0x0048;
16 | public const short OffsetId = 0x0031;
17 | public const short HelpContextId = 0x001E;
18 | public const short CookieId = 0x002C;
19 | public const short TypeModuleId = 0x0021;
20 | public const short TypeClassId = 0x0022;
21 | public const short TypeDocumentId = 0x0022;
22 | public const short ReadOnlyId = 0x0025;
23 | public const short PrivateId = 0x0028;
24 |
25 | public const ushort CookieValue = 0xFFFF;
26 | public const ushort TerminatorValue = 0x002B;
27 | public const uint ReservedValue = 0;
28 |
29 | ///
30 | /// Offset to the module data in the module stream.
31 | ///
32 | ///
33 | /// We always store module at the offset 0 as we do not generate
34 | /// any performance cache data for the module.
35 | ///
36 | public const uint ModuleOffset = 0;
37 |
38 | public ModuleRecord(ModuleUnit module)
39 | {
40 | this.Module = module;
41 | }
42 |
43 | public ModuleUnit Module { get; }
44 |
45 | public string DocString => "";
46 |
47 | public int HelpContext => 0;
48 |
49 | public void WriteTo(BinaryWriter writer)
50 | {
51 | // MODULENAME record
52 | var nameBytes = VbaEncodings.Default.GetBytes(this.Module.Name);
53 | Guard.EnsureNoNullCharacters(nameBytes, "ModuleName", "Module name cannot contain null characters.");
54 |
55 | writer.Write(NameId);
56 | writer.Write(nameBytes.Length);
57 | writer.Write(nameBytes);
58 |
59 | // MODULENAMEUNICODE record
60 | var nameUnicodeBytes = VbaEncodings.UTF16.GetBytes(this.Module.Name);
61 |
62 | writer.Write(NameUnicodeId);
63 | writer.Write(nameUnicodeBytes.Length);
64 | writer.Write(nameUnicodeBytes);
65 |
66 | // MODULESTREAMNAME record
67 | writer.Write(StreamNameId);
68 | writer.Write(nameBytes.Length);
69 | writer.Write(nameBytes);
70 | writer.Write(StreamNameReserved);
71 | writer.Write(nameUnicodeBytes.Length);
72 | writer.Write(nameUnicodeBytes);
73 |
74 | // MODULEDOCSTRING record
75 | var docStringBytes = VbaEncodings.Default.GetBytes(this.DocString);
76 | var docStringUnicodeBytes = VbaEncodings.UTF16.GetBytes(this.DocString);
77 |
78 | writer.Write(DocStringId);
79 | writer.Write(docStringBytes.Length);
80 | writer.Write(docStringBytes);
81 | writer.Write(DocStringReserved);
82 | writer.Write(docStringUnicodeBytes.Length);
83 | writer.Write(docStringUnicodeBytes);
84 |
85 | // MODULEOFFSET record
86 | writer.Write(OffsetId);
87 | writer.Write(sizeof(uint));
88 | writer.Write(ModuleOffset);
89 |
90 | // MODULEHELPCONTEXT record
91 | writer.Write(HelpContextId);
92 | writer.Write(sizeof(uint));
93 | writer.Write(this.HelpContext);
94 |
95 | // MODULECOOKIE record
96 | writer.Write(CookieId);
97 | writer.Write(sizeof(ushort));
98 | writer.Write(CookieValue);
99 |
100 | // MODULETYPE record
101 | var typeId = GetModuleTypeId(this.Module.Type);
102 |
103 | writer.Write(typeId);
104 | writer.Write(ReservedValue);
105 |
106 | // MODULEPRIVATE record
107 | if (this.Module.Type == ModuleUnitType.Class)
108 | {
109 | writer.Write(PrivateId);
110 | writer.Write(ReservedValue);
111 | }
112 |
113 | // Terminator
114 | writer.Write(TerminatorValue);
115 |
116 | // Reserved
117 | writer.Write(ReservedValue);
118 | }
119 |
120 | ///
121 | /// Section 2.3.4.2.3.2.8 MODULETYPE Record
122 | ///
123 | /// MUST be 0x0021 when the containing MODULE Record (section 2.3.4.2.3.2) is a procedural module.
124 | /// MUST be 0x0022 when the containing MODULE Record (section 2.3.4.2.3.2) is a document module, class module, or designer module.
125 | ///
126 | ///
127 | ///
128 | ///
129 | private static short GetModuleTypeId(ModuleUnitType type)
130 | {
131 | return type switch
132 | {
133 | ModuleUnitType.Document => TypeDocumentId,
134 | ModuleUnitType.Module => TypeModuleId,
135 | ModuleUnitType.Class => TypeClassId,
136 | _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
137 | };
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ModuleStream.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 | using OpenMcdf;
6 | using Kavod.Vba.Compression;
7 |
8 | namespace vbamc.Vba
9 | {
10 | public class ModuleStream
11 | {
12 | public ModuleStream(ModuleUnit module)
13 | {
14 | this.Module = module;
15 | }
16 |
17 | public ModuleUnit Module { get; }
18 |
19 | public void WriteTo(CFStorage storage)
20 | {
21 | var streamName = this.Module.Name;
22 | var stream = storage.AddStream(streamName);
23 |
24 | var content = this.Module.ToModuleCode();
25 | var contentBytes = VbaEncodings.Default.GetBytes(content);
26 | var compressedBytes = VbaCompression.Compress(contentBytes);
27 |
28 | stream.SetData(compressedBytes);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ModuleUnit.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 | using System.Diagnostics.Metrics;
6 |
7 | namespace vbamc.Vba
8 | {
9 | public class ModuleUnit
10 | {
11 | public const string ModuleHeaderTemplate = "Attribute VB_Name = \"{0}\"\r\n\r\n";
12 | public const string ClassHeaderTemplate =
13 | "Attribute VB_Name = \"{0}\"\r\n" +
14 | "Attribute VB_Base = \"0{{FCFB3D2A-A0FA-1068-A738-08002B3371B5}}\"\r\n" +
15 | "Attribute VB_GlobalNameSpace = False\r\n" +
16 | "Attribute VB_Creatable = False\r\n" +
17 | "Attribute VB_PredeclaredId = False\r\n" +
18 | "Attribute VB_Exposed = False\r\n" +
19 | "Attribute VB_TemplateDerived = False\r\n" +
20 | "Attribute VB_Customizable = False\r\n" +
21 | "\r\n";
22 | public const string ThisDocumentHeaderTemplate =
23 | "Attribute VB_Name = \"{0}\"\r\n" +
24 | "Attribute VB_Base = \"1Normal.ThisDocument\"\r\n" +
25 | "Attribute VB_GlobalNameSpace = False\r\n" +
26 | "Attribute VB_Creatable = False\r\n" +
27 | "Attribute VB_PredeclaredId = True\r\n" +
28 | "Attribute VB_Exposed = True\r\n" +
29 | "Attribute VB_TemplateDerived = True\r\n" +
30 | "Attribute VB_Customizable = True\r\n" +
31 | "\r\n";
32 |
33 | private ModuleUnit()
34 | {}
35 |
36 | public string Name { get; init; } = default!;
37 |
38 | public ModuleUnitType Type { get; init; } = default!;
39 |
40 | public string Content { get; init; } = default!;
41 |
42 | public string NameForProject => this.Type switch
43 | {
44 | ModuleUnitType.Document => this.Name + "/&H00000000",
45 | _ => this.Name,
46 | };
47 |
48 | public static ModuleUnit FromFile(string path, ModuleUnitType type, string? userProfilePath, bool compileForMacOnWindows = false)
49 | {
50 | var name = Path.GetFileNameWithoutExtension(path);
51 | var content = File.ReadAllText(path);
52 | userProfilePath = userProfilePath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
53 |
54 | content = content.Replace("~/", userProfilePath + (compileForMacOnWindows ? "/" : Path.DirectorySeparatorChar));
55 |
56 | var module = new ModuleUnit
57 | {
58 | Name = name,
59 | Type = type,
60 | Content = content
61 | };
62 |
63 | return module;
64 | }
65 |
66 | public string ToModuleCode()
67 | {
68 | var template = this.Type switch
69 | {
70 | ModuleUnitType.Document => ThisDocumentHeaderTemplate,
71 | ModuleUnitType.Module => ModuleHeaderTemplate,
72 | ModuleUnitType.Class => ClassHeaderTemplate,
73 | _ => throw new ArgumentOutOfRangeException("ModuleUnitType", "ModuleUnitType value is not supported yet.")
74 | };
75 |
76 | var header = string.Format(template, this.Name);
77 |
78 | return header + this.Content;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ModuleUnitType.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | namespace vbamc.Vba
5 | {
6 | public enum ModuleUnitType
7 | {
8 | Document,
9 | Module,
10 | Class,
11 | }
12 | }
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ModulesRecord.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public class ModulesRecord
9 | {
10 | public const short ModulesId = 0x000F;
11 | public const short CookieId = 0x0013;
12 |
13 | public const ushort CookieValue = 0xFFFF;
14 |
15 | public ModulesRecord(ICollection modules)
16 | {
17 | this.Modules = modules;
18 | }
19 |
20 | public ICollection Modules { get; }
21 |
22 | public void WriteTo(BinaryWriter writer)
23 | {
24 | // PROJECTMODULES record
25 | ushort modulesCount = (ushort)this.Modules.Count();
26 |
27 | writer.Write(ModulesId);
28 | writer.Write(sizeof(short));
29 | writer.Write(modulesCount);
30 |
31 | // PROJECTCOOKIE record
32 | writer.Write(CookieId);
33 | writer.Write(sizeof(short));
34 | writer.Write(CookieValue);
35 |
36 | foreach (var module in this.Modules)
37 | {
38 | var moduleRecord = new ModuleRecord(module);
39 | moduleRecord.WriteTo(writer);
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ProjectPassword.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public class ProjectPassword
9 | {
10 | public ProjectPassword(string projectId)
11 | {
12 | ProjectId = projectId;
13 | }
14 |
15 | public string ProjectId { get; }
16 |
17 | public string ToEncryptedString()
18 | {
19 | byte seed = VbaEncryption.GenerateSeed();
20 | return ToEncryptedString(seed);
21 | }
22 |
23 | internal string ToEncryptedString(byte seed)
24 | {
25 | var data = new byte[] { 0 };
26 |
27 | var enc = VbaEncryption.Encrypt(seed, this.ProjectId, data);
28 |
29 | return HexEncoder.BytesToHexString(enc);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ProjectProtection.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | namespace vbamc.Vba
5 | {
6 | [Flags]
7 | public enum ProjectProtection
8 | {
9 | None = 0,
10 | UserProtected = 1,
11 | HostProtected = 2,
12 | VBEProtected = 4,
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ProjectProtectionState.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public class ProjectProtectionState
9 | {
10 | public ProjectProtectionState(string projectId)
11 | {
12 | ProjectId = projectId;
13 | }
14 |
15 | public string ProjectId { get; }
16 |
17 | public ProjectProtection ProjectProtection { get; set; }
18 |
19 | public string ToEncryptedString()
20 | {
21 | byte seed = VbaEncryption.GenerateSeed();
22 | return ToEncryptedString(seed);
23 | }
24 |
25 | internal string ToEncryptedString(byte seed)
26 | {
27 | var enc = VbaEncryption.Encrypt(seed, this.ProjectId, BitConverter.GetBytes(0));
28 |
29 | return HexEncoder.BytesToHexString(enc);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ProjectRecord.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public class ProjectRecord
9 | {
10 | public ProjectRecord()
11 | {
12 | this.HostExtenders = new List()
13 | {
14 | Constants.HostExtender_VBE
15 | };
16 | this.Modules = new List();
17 | }
18 |
19 | public string Id { get; set; } = "";
20 |
21 | public string Name { get; set; } = "";
22 |
23 | public string VersionCompatible32 => Constants.VersionCompatible32;
24 |
25 | ///
26 | /// CMG value.
27 | ///
28 | public string ProtectionState { get; set; } = "";
29 |
30 | ///
31 | /// DPB value.
32 | ///
33 | public string ProjectPassword { get; set; } = "";
34 |
35 | ///
36 | /// GC value.
37 | ///
38 | public string VisibilityState { get; set; } = "";
39 |
40 | public IList HostExtenders { get; }
41 |
42 | public ICollection Modules { get; set; }
43 |
44 | public byte[] Generate()
45 | {
46 | var memory = new MemoryStream();
47 | var writer = new BinaryWriter(memory);
48 |
49 | writer.WriteLine($@"ID=""{this.Id}""");
50 |
51 | foreach (var module in this.Modules.OrderBy(unit => unit.Type))
52 | {
53 | writer.WriteLine($"{module.Type}={module.NameForProject}");
54 | }
55 |
56 | writer.WriteLine($@"Name=""{this.Name}""");
57 | writer.WriteLine($@"HelpContextID=""0""");
58 | writer.WriteLine($@"VersionCompatible32=""{this.VersionCompatible32}""");
59 | writer.WriteLine($@"CMG=""{this.ProtectionState}""");
60 | writer.WriteLine($@"DPB=""{this.ProjectPassword}""");
61 | writer.WriteLine($@"GC=""{this.VisibilityState}""");
62 | writer.WriteLine();
63 |
64 | // HostExtenders
65 | writer.WriteLine($@"[Host Extender Info]");
66 | foreach (var hostExtender in this.HostExtenders)
67 | {
68 | writer.WriteLine(hostExtender);
69 | }
70 |
71 | writer.Close();
72 | return memory.ToArray();
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ProjectVisibilityState.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public class ProjectVisibilityState
9 | {
10 | public const byte ProjectNotVisibleValue = 0x00;
11 | public const byte ProjectVisibleValue = 0xFF;
12 |
13 | public ProjectVisibilityState(string projectId)
14 | {
15 | ProjectId = projectId;
16 | this.IsProjectVisible = true;
17 | }
18 |
19 | public string ProjectId { get; }
20 |
21 | public bool IsProjectVisible { get; set; }
22 |
23 | public string ToEncryptedString()
24 | {
25 | byte seed = VbaEncryption.GenerateSeed();
26 | return ToEncryptedString(seed);
27 | }
28 |
29 | internal string ToEncryptedString(byte seed)
30 | {
31 | var value = this.IsProjectVisible ? ProjectVisibleValue : ProjectNotVisibleValue;
32 | var data = new byte[] { value };
33 |
34 | var enc = VbaEncryption.Encrypt(seed, this.ProjectId, data);
35 |
36 | return HexEncoder.BytesToHexString(enc);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ProjectWmRecord.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 | namespace vbamc.Vba
6 | {
7 | public class ProjectWmRecord
8 | {
9 | public ProjectWmRecord(ICollection modules)
10 | {
11 | this.Modules = modules;
12 | }
13 |
14 | public ICollection Modules { get; }
15 |
16 | public byte[] Generate()
17 | {
18 | using var memory = new MemoryStream();
19 | using var writer = new BinaryWriter(memory);
20 |
21 | foreach (var module in this.Modules)
22 | {
23 | var sName = VbaEncodings.Default.GetBytes(module);
24 | var uName = VbaEncodings.UTF16.GetBytes(module);
25 |
26 | // ModuleName
27 | writer.Write(sName);
28 | writer.Write(VbaEncodings.NULL);
29 |
30 | // ModuleNameUnicode
31 | writer.Write(uName);
32 | writer.Write(VbaEncodings.NULL);
33 | writer.Write(VbaEncodings.NULL);
34 | }
35 |
36 | // Terminator (2-bytes) (0x0000)
37 | writer.Write(VbaEncodings.NULL);
38 | writer.Write(VbaEncodings.NULL);
39 |
40 | writer.Close();
41 | return memory.ToArray();
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ReferenceProjectRecord.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | namespace vbamc.Vba
5 | {
6 | public class ReferenceProjectRecord : ReferenceRecord
7 | {
8 | public ReferenceProjectRecord()
9 | : base(ReferenceRecordType.Project)
10 | {
11 | }
12 |
13 | public string LibidAbsolute { get; set; } = "";
14 |
15 | public string LibidRelative { get; set; } = "";
16 |
17 | protected override void WriteInternalTo(BinaryWriter writer)
18 | {
19 | // REFERENCEPROJECT record
20 | var libidAbsoluteBytes = VbaEncodings.Default.GetBytes(this.LibidAbsolute);
21 | var sizeOfLibidAbsolute = libidAbsoluteBytes.Length;
22 | var libidRelativeBytes = VbaEncodings.Default.GetBytes(this.LibidRelative);
23 | var sizeOfLibidRelative = libidAbsoluteBytes.Length;
24 |
25 | Guard.EnsureNoNullCharacters(libidAbsoluteBytes, nameof(LibidAbsolute), "LibidAbsolute cannot contain null characters.");
26 | Guard.EnsureNoNullCharacters(libidRelativeBytes, nameof(LibidRelative), "LibidRelative cannot contain null characters.");
27 |
28 | int size = sizeof(int) + sizeOfLibidAbsolute + sizeof(int) + sizeOfLibidRelative + sizeof(uint) + sizeof(ushort);
29 |
30 | writer.Write((ushort)this.Type);
31 | writer.Write(size);
32 | writer.Write(sizeOfLibidAbsolute);
33 | writer.Write(libidAbsoluteBytes);
34 | writer.Write(sizeOfLibidRelative);
35 | writer.Write(libidRelativeBytes);
36 | writer.Write((uint)1706735520);
37 | writer.Write((ushort)0x07);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ReferenceRecord.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | namespace vbamc.Vba
5 | {
6 | public class ReferenceRecord
7 | {
8 | public const short ReferenceNameId = 0x0016;
9 | public const short ReferenceNameReserved = 0x003E;
10 |
11 | protected ReferenceRecord(ReferenceRecordType type)
12 | {
13 | this.Type = type;
14 | }
15 |
16 | public ReferenceRecordType Type { get; }
17 |
18 | public string ReferenceName { get; set; } = "";
19 |
20 | public void WriteTo(BinaryWriter writer)
21 | {
22 | // REFERENCENAME record
23 | var nameBytes = VbaEncodings.Default.GetBytes(this.ReferenceName);
24 | var nameUnicodeBytes = VbaEncodings.UTF16.GetBytes(this.ReferenceName);
25 |
26 | Guard.EnsureNoNullCharacters(nameBytes, nameof(ReferenceName), "Reference name cannot contain null characters.");
27 |
28 | writer.Write(ReferenceNameId);
29 | writer.Write(nameBytes.Length);
30 | writer.Write(nameBytes);
31 | writer.Write(ReferenceNameReserved);
32 | writer.Write(nameUnicodeBytes.Length);
33 | writer.Write(nameUnicodeBytes);
34 |
35 | // Reference record: CONTROL / ORIGINAL / REGISTERED / PROJECT
36 | // For purposes of this implementation, we're only interested in the REGISTERED record.
37 |
38 | // REFERENCEREGISTERED record
39 | this.WriteInternalTo(writer);
40 | }
41 |
42 | protected virtual void WriteInternalTo(BinaryWriter writer)
43 | {
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ReferenceRecordType.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public enum ReferenceRecordType : ushort
9 | {
10 | Control = 0x002F,
11 | Original = 0x0033,
12 | Registered = 0x000D,
13 | Project = 0x000E
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ReferenceRegisteredRecord.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | namespace vbamc.Vba
5 | {
6 | public class ReferenceRegisteredRecord : ReferenceRecord
7 | {
8 | public ReferenceRegisteredRecord()
9 | : base(ReferenceRecordType.Registered)
10 | {
11 | }
12 |
13 | public string Libid { get; set; } = "";
14 |
15 | protected override void WriteInternalTo(BinaryWriter writer)
16 | {
17 | // REFERENCEREGISTERED record
18 | var libidBytes = VbaEncodings.Default.GetBytes(this.Libid);
19 | var sizeOfLibid = libidBytes.Length;
20 |
21 | Guard.EnsureNoNullCharacters(libidBytes, nameof(Libid), "Libid cannot contain null characters.");
22 |
23 | int size = sizeof(int) + sizeOfLibid + sizeof(uint) + sizeof(ushort);
24 |
25 | writer.Write((ushort)this.Type);
26 | writer.Write(size);
27 | writer.Write(sizeOfLibid);
28 | writer.Write(libidBytes);
29 | writer.Write((uint)0);
30 | writer.Write((ushort)0);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/ReferencesRecord.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | namespace vbamc.Vba
5 | {
6 | public class ReferencesRecord
7 | {
8 | public void WriteTo(BinaryWriter writer)
9 | {
10 | var stdole = new ReferenceRegisteredRecord
11 | {
12 | ReferenceName = "stdole",
13 | Libid = @"*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\SysWOW64\stdole2.tlb#OLE Automation"
14 | };
15 |
16 | // TODO: change to ProjectReferenceRecord
17 | var wordNormalDocument = new ReferenceProjectRecord
18 | {
19 | ReferenceName = "Normal",
20 | LibidAbsolute = @"*\CNormal",
21 | LibidRelative = @"*\CNormal"
22 | };
23 |
24 | var office = new ReferenceRegisteredRecord
25 | {
26 | ReferenceName = "Office",
27 | Libid = @"*\G{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}#2.0#0#C:\Program Files (x86)\Common Files\Microsoft Shared\OFFICE16\MSO.DLL#Microsoft Office 16.0 Object Library"
28 | };
29 |
30 | // PROJECTREFERENCES record
31 | stdole.WriteTo(writer);
32 |
33 | // TODO: fix
34 | // if (project.IsTargetWordMacro)
35 | // wordNormalDocument.WriteTo(writer);
36 |
37 | office.WriteTo(writer);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/StorageId.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public static class StorageId
9 | {
10 | public static readonly string VBA = "VBA";
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/StreamId.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public static class StreamId
9 | {
10 | public static readonly string Project = "PROJECT";
11 | public static readonly string ProjectWm = "PROJECTwm";
12 |
13 | public static readonly string VbaProject = "_VBA_PROJECT";
14 | public static readonly string Dir = "dir";
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/SysKind.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public enum SysKind
9 | {
10 | Win16 = 0x00,
11 | Win32 = 0x01,
12 | Mac = 0x02,
13 | Win64 = 0x03,
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/VbaEncodings.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 | using System.Text;
6 |
7 | namespace vbamc.Vba
8 | {
9 | public static class VbaEncodings
10 | {
11 | public const int DefaultCodePage = 1252;
12 | public const byte NULL = 0x00;
13 | public static readonly byte[] NewLine = new byte[] { 0x0D, 0x0A };
14 |
15 | public static readonly Encoding Default = Encoding.GetEncoding(DefaultCodePage);
16 | public static readonly Encoding UTF16 = Encoding.Unicode;
17 |
18 | public static void WriteLine(this BinaryWriter writer, string value)
19 | {
20 | var bytes = Default.GetBytes(value);
21 | writer.Write(bytes);
22 | writer.Write(NewLine);
23 | }
24 |
25 | public static void WriteLine(this BinaryWriter writer)
26 | {
27 | writer.Write(NewLine);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/VbaEncryption.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 | using System.Security.Cryptography;
6 | using System.Text;
7 |
8 | namespace vbamc.Vba
9 | {
10 | public class VbaEncryption
11 | {
12 | public const byte EncryptionVersion = 2;
13 |
14 | public static Func CreateRandomGenerator { get; set; } = () => RandomNumberGenerator.Create();
15 |
16 | public static byte GenerateSeed()
17 | {
18 | var rnd = CreateRandomGenerator();
19 | var buffer = new byte[1];
20 | rnd.GetNonZeroBytes(buffer);
21 | return buffer[0];
22 | }
23 |
24 | public static byte GetProjectKey(string projectId)
25 | {
26 | var buffer = Encoding.ASCII.GetBytes(projectId);
27 |
28 | byte projectKey = 0;
29 |
30 | foreach (var b in buffer)
31 | {
32 | projectKey += b;
33 | }
34 |
35 | return projectKey;
36 | }
37 |
38 | public static ReadOnlySpan Encrypt(byte seed, string projectId, ReadOnlySpan data)
39 | {
40 | var rnd = CreateRandomGenerator();
41 | var memory = new MemoryStream(1024);
42 | var writer = new BinaryWriter(memory);
43 |
44 | var dataLength = data.Length;
45 | byte projectKey = GetProjectKey(projectId);
46 |
47 | byte versionEnc = (byte)(seed ^ EncryptionVersion);
48 | byte projectKeyEnc = (byte)(seed ^ projectKey);
49 |
50 | byte unencryptedByte = projectKey;
51 | byte encryptedByte1 = projectKeyEnc;
52 | byte encryptedByte2 = versionEnc;
53 |
54 | writer.Write(seed);
55 | writer.Write(versionEnc);
56 | writer.Write(projectKeyEnc);
57 |
58 | int ignoredLength = (seed & 6) / 2;
59 | var tempBytes = new byte[ignoredLength];
60 | rnd.GetNonZeroBytes(tempBytes);
61 |
62 | for (int i = 0; i < ignoredLength; i++)
63 | {
64 | byte tmp = tempBytes[i];
65 | byte byteEnc = (byte)(tmp ^ (encryptedByte2 + unencryptedByte));
66 | writer.Write(byteEnc);
67 |
68 | encryptedByte2 = encryptedByte1;
69 | encryptedByte1 = byteEnc;
70 | unencryptedByte = tmp;
71 | }
72 |
73 | var dataLengthBytes = BitConverter.GetBytes(dataLength);
74 | for (int i = 0; i < dataLengthBytes.Length; i++)
75 | {
76 | byte tmp = dataLengthBytes[i];
77 | byte byteEnc = (byte)(tmp ^ (encryptedByte2 + unencryptedByte));
78 | writer.Write(byteEnc);
79 |
80 | encryptedByte2 = encryptedByte1;
81 | encryptedByte1 = byteEnc;
82 | unencryptedByte = tmp;
83 | }
84 |
85 | for (int i = 0; i < data.Length; i++)
86 | {
87 | byte tmp = data[i];
88 | byte byteEnc = (byte)(tmp ^ (encryptedByte2 + unencryptedByte));
89 | writer.Write(byteEnc);
90 |
91 | encryptedByte2 = encryptedByte1;
92 | encryptedByte1 = byteEnc;
93 | unencryptedByte = tmp;
94 | }
95 |
96 | writer.Close();
97 | return memory.ToArray();
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/src/VbaCompiler/Vba/VbaProjectStream.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbamc.Vba
7 | {
8 | public class VbaProjectStream
9 | {
10 | ///
11 | /// Version generated by Office 2003.
12 | ///
13 | public const short VersionOffice2003 = 0x0079;
14 |
15 | ///
16 | /// Version generated by Office 365.
17 | ///
18 | public const short VersionOffice365 = 0x00B2;
19 |
20 | ///
21 | /// Implementations other the Office applications must use the version
22 | /// number 0xFFFF so that performance caches are ignored.
23 | ///
24 | public const ushort Version = 0xFFFF;
25 |
26 | // Reserved constants
27 | public const short Reserved1 = 0x61CC;
28 | public const byte Reserved2 = 0x00;
29 | public const short Reserved3 = 0x0001;
30 |
31 | public byte[] Generate()
32 | {
33 | var memory = new MemoryStream();
34 | var writer = new BinaryWriter(memory);
35 |
36 | // _VBA_PROJECT stream header
37 | writer.Write(Reserved1);
38 | // (1.6) To be interoperable, this version number MUST be set to 0xFFFF so that performance caches are ignored.
39 | writer.Write(Version);
40 | writer.Write(Reserved2);
41 | writer.Write(Reserved3);
42 |
43 | // omit the PerformanceCache record (see 1.6 Versioning and Localization)
44 |
45 | writer.Close();
46 | return memory.ToArray();
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/VbaCompiler/VbaCompiler.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 | using DocumentFormat.OpenXml;
6 | using DocumentFormat.OpenXml.Office2010.CustomUI;
7 | using DocumentFormat.OpenXml.Packaging;
8 | using DocumentFormat.OpenXml.CustomProperties;
9 | using Kavod.Vba.Compression;
10 | using OpenMcdf;
11 | using vbamc.Vba;
12 |
13 | namespace vbamc
14 | {
15 | public class VbaCompiler
16 | {
17 | private IList modules = new List();
18 |
19 | public IDictionary ExtendedProperties { get; set; } = new Dictionary();
20 |
21 | public Guid ProjectId { get; set; }
22 |
23 | public string ProjectName { get; set; } = "Project";
24 |
25 | public string? ProjectVersion { get; set; }
26 |
27 | public string? CompanyName { get; set; }
28 |
29 | public string? UserProfilePath { get; set; }
30 |
31 | ///
32 | /// Set to true for instance of VbaCompiler if generating add-ins on windows but are used on macOS to
33 | /// explicitly set directory separator char for macOS - otherwise platform default is used
34 | ///
35 | public bool CompileForMacOnWindows { get; set; } = false;
36 |
37 | public void AddModule(string path)
38 | {
39 | var module = ModuleUnit.FromFile(path, ModuleUnitType.Module, this.UserProfilePath, this.CompileForMacOnWindows);
40 | this.modules.Add(module);
41 | }
42 |
43 | public void AddClass(string path)
44 | {
45 | var @class = ModuleUnit.FromFile(path, ModuleUnitType.Class, this.UserProfilePath, this.CompileForMacOnWindows);
46 | this.modules.Add(@class);
47 | }
48 |
49 | public void AddThisDocument(string path)
50 | {
51 | var document = ModuleUnit.FromFile(path, ModuleUnitType.Document, this.UserProfilePath, this.CompileForMacOnWindows);
52 | this.modules.Add(document);
53 | }
54 |
55 | public MemoryStream CompileVbaProject()
56 | {
57 | var vbaProjectMemory = new MemoryStream();
58 | this.CompileVbaProject(vbaProjectMemory);
59 | vbaProjectMemory.Position = 0;
60 |
61 | return vbaProjectMemory;
62 | }
63 |
64 | public void CompileVbaProject(Stream stream)
65 | {
66 | var moduleNames = this.modules.OrderBy(m => m.Type).Select(m => m.Name).ToList();
67 |
68 | var storage = new CompoundFile();
69 | var projectId = this.ProjectId.ToString("B").ToUpperInvariant();
70 |
71 | // PROJECT stream
72 | var projectStream = storage.RootStorage.AddStream(StreamId.Project);
73 | var project = new ProjectRecord();
74 | project.Id = projectId;
75 | project.Name = this.ProjectName;
76 | project.Modules = this.modules;
77 |
78 | var protectionState = new ProjectProtectionState(projectId);
79 | var projectPassword = new ProjectPassword(projectId);
80 | var visibilityState = new ProjectVisibilityState(projectId);
81 |
82 | project.ProtectionState = protectionState.ToEncryptedString();
83 | project.ProjectPassword = projectPassword.ToEncryptedString();
84 | project.VisibilityState = visibilityState.ToEncryptedString();
85 |
86 | var projectContent = project.Generate();
87 | projectStream.SetData(projectContent);
88 |
89 | // PROJECTwm stream
90 | var projectWmStream = storage.RootStorage.AddStream(StreamId.ProjectWm);
91 | var projectWm = new ProjectWmRecord(moduleNames);
92 | var projectWmContent = projectWm.Generate();
93 | projectWmStream.SetData(projectWmContent);
94 |
95 | // VBA storage
96 | var vbaStorage = storage.RootStorage.AddStorage(StorageId.VBA);
97 |
98 | // _VBA_PROJECT stream
99 | var vbaProjectStream = vbaStorage.AddStream(StreamId.VbaProject);
100 | var vbaProject = new VbaProjectStream();
101 | var vbaProjectContent = vbaProject.Generate();
102 | vbaProjectStream.SetData(vbaProjectContent);
103 |
104 | // dir stream
105 | var dirStream = vbaStorage.AddStream(StreamId.Dir);
106 | var dir = new DirStream();
107 | var dirContent = dir.GetData(project);
108 | var compressed = VbaCompression.Compress(dirContent);
109 | dirStream.SetData(compressed);
110 |
111 | // module streams
112 | foreach (var module in this.modules)
113 | {
114 | var moduleStream = new ModuleStream(module);
115 | moduleStream.WriteTo(vbaStorage);
116 | }
117 |
118 | storage.Save(stream);
119 | }
120 |
121 | public void CompilePowerPointMacroFile(Stream outputMacroFileStream, Stream vbaProjectStream, PresentationDocumentType documentType, string? customSourcePath = null)
122 | {
123 | var macroTemplatePath = Path.Combine(AppContext.BaseDirectory, @"data/MacroTemplate.potm");
124 | var macroTemplate = PresentationDocument.CreateFromTemplate(macroTemplatePath);
125 | var mainDoc = macroTemplate.PresentationPart;
126 | if (mainDoc != null)
127 | {
128 | var vbaProject = mainDoc.AddNewPart();
129 | vbaProject.FeedData(vbaProjectStream);
130 | }
131 |
132 | var ribbonPart = macroTemplate.RibbonAndBackstageCustomizationsPart ?? macroTemplate.AddRibbonAndBackstageCustomizationsPart();
133 | AttachRibbonCustomization(ribbonPart, customSourcePath ?? Directory.GetCurrentDirectory());
134 |
135 | macroTemplate.PackageProperties.Title = this.ProjectName;
136 | macroTemplate.PackageProperties.Version = this.ProjectVersion;
137 | var extendedProperties = macroTemplate.ExtendedFilePropertiesPart?.Properties;
138 |
139 | var propCompany = extendedProperties?.Company;
140 | if (propCompany != null && !string.IsNullOrEmpty(this.CompanyName))
141 | {
142 | propCompany.Text = this.CompanyName;
143 | }
144 |
145 | AddExtendedProperties(extendedProperties);
146 |
147 | macroTemplate.ChangeDocumentType(documentType);
148 | using var tempMacroFile = macroTemplate.Clone(outputMacroFileStream);
149 | }
150 |
151 | public void CompileExcelMacroFile(Stream outputMacroFileStream, Stream vbaProjectStream, SpreadsheetDocumentType documentType, string? customSourcePath = null)
152 | {
153 | var macroTemplatePath = Path.Combine(AppContext.BaseDirectory, @"data/MacroTemplate.xltx");
154 | var macroTemplate = SpreadsheetDocument.CreateFromTemplate(macroTemplatePath);
155 | var mainDoc = macroTemplate.WorkbookPart;
156 | if (mainDoc != null)
157 | {
158 | var vbaProject = mainDoc.AddNewPart();
159 | vbaProject.FeedData(vbaProjectStream);
160 | }
161 |
162 | var ribbonPart = macroTemplate.RibbonAndBackstageCustomizationsPart ?? macroTemplate.AddRibbonAndBackstageCustomizationsPart();
163 | AttachRibbonCustomization(ribbonPart, customSourcePath ?? Directory.GetCurrentDirectory());
164 |
165 | macroTemplate.PackageProperties.Title = this.ProjectName;
166 | macroTemplate.PackageProperties.Version = this.ProjectVersion;
167 | var extendedProperties = macroTemplate.ExtendedFilePropertiesPart?.Properties;
168 |
169 | var propCompany = extendedProperties?.Company;
170 | if (propCompany != null && !string.IsNullOrEmpty(this.CompanyName))
171 | {
172 | propCompany.Text = this.CompanyName;
173 | }
174 |
175 | AddExtendedProperties(extendedProperties);
176 |
177 | macroTemplate.ChangeDocumentType(documentType);
178 | using var macroFile = macroTemplate.Clone(outputMacroFileStream);
179 | }
180 |
181 | public void CompileWordMacroFile(Stream outputMacroFileStream, Stream vbaProjectStream, WordprocessingDocumentType documentType, string? customSourcePath = null)
182 | {
183 | if (documentType != WordprocessingDocumentType.MacroEnabledDocument)
184 | {
185 | throw new ArgumentOutOfRangeException(nameof(documentType), "Compiler supports only WordprocessingDocumentType.MacroEnabledDocument value.");
186 | }
187 |
188 | var macroTemplatePath = Path.Combine(AppContext.BaseDirectory, @"data/MacroTemplate.dotx");
189 | var macroTemplate = WordprocessingDocument.CreateFromTemplate(macroTemplatePath);
190 | var mainDoc = macroTemplate.MainDocumentPart;
191 | if (mainDoc != null)
192 | {
193 | var vbaProject = mainDoc.AddNewPart();
194 | vbaProject.FeedData(vbaProjectStream);
195 | }
196 |
197 | var ribbonPart = macroTemplate.RibbonAndBackstageCustomizationsPart ?? macroTemplate.AddRibbonAndBackstageCustomizationsPart();
198 | AttachRibbonCustomization(ribbonPart, customSourcePath ?? Directory.GetCurrentDirectory());
199 |
200 | macroTemplate.PackageProperties.Title = this.ProjectName;
201 | macroTemplate.PackageProperties.Version = this.ProjectVersion;
202 | var extendedProperties = macroTemplate.ExtendedFilePropertiesPart?.Properties;
203 | var propCompany = extendedProperties?.Company;
204 | if (propCompany != null && !string.IsNullOrEmpty(this.CompanyName))
205 | {
206 | propCompany.Text = this.CompanyName;
207 | }
208 |
209 | AddExtendedProperties(extendedProperties);
210 |
211 | macroTemplate.ChangeDocumentType(documentType);
212 | using var macroFile = macroTemplate.Clone(outputMacroFileStream);
213 | }
214 |
215 | private void AttachRibbonCustomization(RibbonAndBackstageCustomizationsPart ribbonPart, string sourcePath)
216 | {
217 | var customUiDir = Path.Combine(sourcePath, "customUI");
218 | var ribbonPath = Path.Combine(customUiDir, "customUI14.xml");
219 | if (!File.Exists(ribbonPath))
220 | {
221 | return;
222 | }
223 |
224 | var ribbonContent = File.ReadAllText(ribbonPath);
225 | ribbonPart.CustomUI = new CustomUI(ribbonContent);
226 | ribbonPart.CustomUI.Save();
227 | //Console.WriteLine($"Added ribbon customization from file '{ribbonPath}'");
228 |
229 | var images = Directory.EnumerateFiles(Path.Combine(customUiDir, "images"), "*.png");
230 | foreach (var imagePath in images)
231 | {
232 | var imageFilename = Path.GetFileNameWithoutExtension(imagePath);
233 | var imagePart = ribbonPart.AddImagePart(ImagePartType.Png, imageFilename);
234 | using var imageStream = new FileStream(imagePath, FileMode.Open, FileAccess.Read);
235 | imagePart.FeedData(imageStream);
236 | }
237 | }
238 |
239 | private void AddExtendedProperties(DocumentFormat.OpenXml.ExtendedProperties.Properties? extendedProperties)
240 | {
241 | foreach (var property in this.ExtendedProperties)
242 | {
243 | var newProperty = new CustomDocumentProperty
244 | {
245 | Name = property.Key,
246 | InnerXml = property.Value
247 | };
248 | extendedProperties?.AppendChild(newProperty);
249 | }
250 | extendedProperties?.Save();
251 | }
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/src/VbaCompiler/VbaCompiler.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | NetOfficeFw.VbaCompiler
5 |
6 |
7 |
8 | Library with compiler for Visual Basic for Applications projects. Source code files and Ribbon customizations are compiled to Microsoft Office macro files.
9 | library;compiler;VBA;macro;addin;office
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/vbamc/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/src/vbamc/Icon.png
--------------------------------------------------------------------------------
/src/vbamc/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System.ComponentModel.DataAnnotations;
5 | using System.Text;
6 | using DocumentFormat.OpenXml;
7 | using McMaster.Extensions.CommandLineUtils;
8 | using vbamc;
9 |
10 | [Command(Name = "vbamc", Description = "Visual Basic for Applications macro compiler")]
11 | public class Program
12 | {
13 | public static int Main(string[] args)
14 | => CommandLineApplication.Execute(args);
15 |
16 | [Required]
17 | [Option("-m|--module")]
18 | public IEnumerable Modules { get; } = Enumerable.Empty();
19 |
20 | [Option("-c|--class")]
21 | public IEnumerable Classes { get; } = Enumerable.Empty();
22 |
23 | // [Option("-d|--document")]
24 | // public string? Document { get; }
25 |
26 | [Option("-n|--name", Description = "Project name")]
27 | public string ProjectName { get; } = "VBAProject";
28 |
29 | [Option("--company", Description = "Company name")]
30 | public string? CompanyName { get; }
31 |
32 | [Option("-f|--file", Description = "Target add-in file name with extension")]
33 | public string FileName { get; } = "PresentationAddin.ppam";
34 |
35 | [Option("-o|--output", Description = "Target build output path")]
36 | public string OutputPath { get; } = "bin";
37 |
38 | [Option("--user-profile-path", Description = "Path to the user profile to replace the ~/ expression")]
39 | public string? UserProfilePath { get; }
40 |
41 | [Option("-p|--property", Description = "Extended property in the format name=value")]
42 | public string[] ExtendedProperties { get; } = Array.Empty();
43 |
44 | private void OnExecute()
45 | {
46 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
47 |
48 | var wd = Directory.GetCurrentDirectory();
49 | var outputPath = Path.Combine(wd, this.OutputPath);
50 |
51 | var compiler = new VbaCompiler();
52 |
53 | compiler.ProjectId = Guid.NewGuid();
54 | compiler.ProjectName = this.ProjectName;
55 | compiler.CompanyName = this.CompanyName;
56 | compiler.UserProfilePath = this.UserProfilePath;
57 |
58 | // // add document module
59 | // if (this.Document != null)
60 | // {
61 | // var path = this.Document;
62 | // if (!Path.IsPathRooted(path))
63 | // {
64 | // path = Path.Combine(wd, path);
65 | // }
66 |
67 | // compiler.AddThisDocument(path);
68 | // }
69 |
70 | // add modules
71 | foreach (var module in this.Modules)
72 | {
73 | var path = module;
74 | if (!Path.IsPathRooted(path))
75 | {
76 | path = Path.Combine(wd, path);
77 | }
78 |
79 | compiler.AddModule(path);
80 | }
81 |
82 | // add classes
83 | foreach (var @class in this.Classes)
84 | {
85 | var path = @class;
86 | if (!Path.IsPathRooted(path))
87 | {
88 | path = Path.Combine(wd, path);
89 | }
90 |
91 | compiler.AddClass(path);
92 | }
93 |
94 | foreach (var property in this.ExtendedProperties)
95 | {
96 | var parts = property.Split('=');
97 | compiler.ExtendedProperties.Add(parts[0], parts[1]);
98 | }
99 |
100 | DirectoryEx.EnsureDirectory(outputPath);
101 | using var outputMacroFile = File.Create(Path.Combine(outputPath, this.FileName));
102 | var vbaProjectMemory = compiler.CompileVbaProject();
103 |
104 | var extension = Path.GetExtension(this.FileName).ToLowerInvariant();
105 | switch (extension)
106 | {
107 | // Microsoft PowerPoint
108 | case ".pptm":
109 | compiler.CompilePowerPointMacroFile(outputMacroFile, vbaProjectMemory, PresentationDocumentType.MacroEnabledPresentation);
110 | break;
111 | case ".ppam":
112 | compiler.CompilePowerPointMacroFile(outputMacroFile, vbaProjectMemory, PresentationDocumentType.AddIn);
113 | break;
114 |
115 | // Microsoft Excel
116 | case ".xlsm":
117 | compiler.CompileExcelMacroFile(outputMacroFile, vbaProjectMemory, SpreadsheetDocumentType.MacroEnabledWorkbook);
118 | break;
119 | case ".xlam":
120 | compiler.CompileExcelMacroFile(outputMacroFile, vbaProjectMemory, SpreadsheetDocumentType.AddIn);
121 | break;
122 |
123 | // Microsoft Word
124 | case ".docm":
125 | compiler.CompileWordMacroFile(outputMacroFile, vbaProjectMemory, WordprocessingDocumentType.MacroEnabledDocument);
126 | break;
127 |
128 | default:
129 | throw new NotSupportedException($"File extension {extension} is not supported.");
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/vbamc/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "vbamc": {
4 | "commandName": "Project",
5 | "commandLineArgs": "-m Module1.vb -c Class1.vb -f \"Acme.ppam\" --company \"ACME\"",
6 | "workingDirectory": "d:\\dev\\github\\NetOfficeFw\\vbamc\\sample\\"
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/src/vbamc/README.md:
--------------------------------------------------------------------------------
1 | Compiler `vbamc` compiles Visual Basic source code and Ribbon customizations to a macro enabled add-in file for Microsoft Office applications.
2 |
3 | ### Usage
4 |
5 | Sample usage to generate PowerPoint presentation file with macro
6 | from the source code files `Module.vb` and `MyClass.vb`:
7 |
8 | ```shell
9 | vbamc --module Module.vb --class MyClass.vb --name "VBA Project" --company "ACME" --file Presentation.ppam
10 | ```
11 |
12 | ### Requirements
13 |
14 | The compiler works on .NET 6, 7 and 8 runtimes on Windows and macOS.
15 |
16 | ### Samples
17 |
18 | Discover samples in our repository at
19 |
20 |
21 | _Project icon [Online Coding][1] is licensed from Icons8 service under Universal Multimedia Licensing Agreement for Icons8._
22 | _See for more information_
23 |
24 | [1]: https://icons8.com/icon/UVQTFk728g0D/online-coding
25 |
--------------------------------------------------------------------------------
/src/vbamc/data/MacroTemplate.dotx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/src/vbamc/data/MacroTemplate.dotx
--------------------------------------------------------------------------------
/src/vbamc/data/MacroTemplate.potm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/src/vbamc/data/MacroTemplate.potm
--------------------------------------------------------------------------------
/src/vbamc/data/MacroTemplate.xltx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/src/vbamc/data/MacroTemplate.xltx
--------------------------------------------------------------------------------
/src/vbamc/vbamc.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | true
6 |
7 |
8 |
9 | Compiler vbamc compiles Visual Basic source code and Ribbon customizations to a macro enabled add-in file for Microsoft Office applications.
10 | tool;compiler;VBA;macro;addin;office
11 |
12 |
13 |
14 | ../../sample
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | PreserveNewest
28 |
29 |
30 | PreserveNewest
31 |
32 |
33 | PreserveNewest
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/tests/VbaCompiler.Benchmark/CompileMacroBenchmark.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using BenchmarkDotNet.Attributes;
3 | using DocumentFormat.OpenXml;
4 | using vbamc;
5 |
6 | public class CompileMacroBenchmark
7 | {
8 | private readonly Guid ProjectId = Guid.NewGuid();
9 |
10 | [GlobalSetup]
11 | public void Setup()
12 | {
13 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
14 | }
15 |
16 | [Benchmark]
17 | public MemoryStream CompileVbaProject()
18 | {
19 | // Arrang
20 | var sourcePath = Path.Combine(Directory.GetCurrentDirectory(), "data");
21 | var classPath = Path.Combine(sourcePath, "Class.vb");
22 | var modulePath = Path.Combine(sourcePath, "Module.vb");
23 |
24 | var compiler = new VbaCompiler()
25 | {
26 | ProjectId = this.ProjectId,
27 | ProjectName = "Project A",
28 | ProjectVersion = "1.0.0",
29 | CompanyName = "ACME"
30 | };
31 | compiler.AddModule(modulePath);
32 | compiler.AddClass(classPath);
33 |
34 | // Act
35 | var macro = new MemoryStream();
36 | var vbaProjectStream = compiler.CompileVbaProject();
37 | compiler.CompilePowerPointMacroFile(macro, vbaProjectStream, PresentationDocumentType.AddIn);
38 | return macro;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/VbaCompiler.Benchmark/CompileVbaProjectBenchmark.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using BenchmarkDotNet.Attributes;
3 | using vbamc;
4 |
5 | public class CompileVbaProjectBenchmark
6 | {
7 | private readonly Guid ProjectId = Guid.NewGuid();
8 |
9 | [GlobalSetup]
10 | public void Setup()
11 | {
12 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
13 | }
14 |
15 | [Benchmark]
16 | public MemoryStream CompileVbaProject()
17 | {
18 | // Arrang
19 | var sourcePath = Path.Combine(Directory.GetCurrentDirectory(), "data");
20 | var classPath = Path.Combine(sourcePath, "Class.vb");
21 | var modulePath = Path.Combine(sourcePath, "Module.vb");
22 |
23 | var compiler = new VbaCompiler()
24 | {
25 | ProjectId = this.ProjectId,
26 | ProjectName = "Project A",
27 | ProjectVersion = "1.0.0",
28 | CompanyName = "ACME"
29 | };
30 | compiler.AddModule(modulePath);
31 | compiler.AddClass(classPath);
32 |
33 | var vbaProjectMemory = new MemoryStream();
34 |
35 | // Act
36 | compiler.CompileVbaProject(vbaProjectMemory);
37 | return vbaProjectMemory;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/VbaCompiler.Benchmark/Program.cs:
--------------------------------------------------------------------------------
1 | // See https://benchmarkdotnet.org/articles/guides/how-it-works.html for more information
2 |
3 | using BenchmarkDotNet.Running;
4 |
5 | // var summary = BenchmarkRunner.Run();
6 | var summary = BenchmarkRunner.Run();
7 |
--------------------------------------------------------------------------------
/tests/VbaCompiler.Benchmark/VbaCompiler.Benchmark.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | PreserveNewest
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 | PreserveNewest
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/tests/VbaCompiler.Benchmark/data/Class.vb:
--------------------------------------------------------------------------------
1 | ' Empty class file
2 |
--------------------------------------------------------------------------------
/tests/VbaCompiler.Benchmark/data/MacroTemplate.potm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/tests/VbaCompiler.Benchmark/data/MacroTemplate.potm
--------------------------------------------------------------------------------
/tests/VbaCompiler.Benchmark/data/Module.vb:
--------------------------------------------------------------------------------
1 | ' Empty module file
2 |
--------------------------------------------------------------------------------
/tests/VbaCompiler.Tests/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using NUnit.Framework;
2 | global using NUnit.Framework.Legacy;
3 | global using vbamc.Vba;
4 |
--------------------------------------------------------------------------------
/tests/VbaCompiler.Tests/Streams/VbaCompilerStreamsTests.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml;
2 | using System;
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace vbamc.Tests.Streams
7 | {
8 | class VbaCompilerStreamsTests
9 | {
10 | [Test]
11 | public void Test1()
12 | {
13 | // Arrange
14 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
15 |
16 | var output = Path.Combine(TestContext.CurrentContext.TestDirectory, DateTimeOffset.Now.ToUnixTimeSeconds().ToString());
17 | Directory.CreateDirectory(output);
18 |
19 | var sourcePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "data");
20 | var classPath = Path.Combine(sourcePath, "Class.vb");
21 | var modulePath = Path.Combine(sourcePath, "Module.vb");
22 |
23 | var compiler = new VbaCompiler()
24 | {
25 | ProjectId = Guid.NewGuid(),
26 | ProjectName = "Project A",
27 | ProjectVersion = "1.0.0",
28 | CompanyName = "ACME"
29 | };
30 | compiler.AddModule(modulePath);
31 | compiler.AddClass(classPath);
32 |
33 | var vbaProjectMemory = new MemoryStream();
34 | var powerpointAddinMemory = new MemoryStream();
35 |
36 | // Act
37 | compiler.CompileVbaProject(vbaProjectMemory);
38 | compiler.CompilePowerPointMacroFile(powerpointAddinMemory, vbaProjectMemory, PresentationDocumentType.MacroEnabledPresentation);
39 |
40 | // Assert
41 | ClassicAssert.Greater(powerpointAddinMemory.Length, 1);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/VbaCompiler.Tests/VbaCompiler.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 |
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | PreserveNewest
18 |
19 |
20 | PreserveNewest
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | all
30 | runtime; build; native; contentfiles; analyzers; buildtransitive
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/tests/VbaCompiler.Tests/VbaEncryptionTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace VbaCompiler.Tests
7 | {
8 | public class VbaEncryptionTests
9 | {
10 | [SetUp]
11 | public void SetUp()
12 | {
13 | VbaEncryption.CreateRandomGenerator = () => new VbaSamplesNumberGenerator();
14 | }
15 |
16 | [Test]
17 | [TestCase("{917DED54-440B-4FD1-A5C1-74ACF261E600}", (ushort)0xDF)]
18 | public void GetProjectKey_SampleProjectId_ComputesChecksumForEncryption(string projectId, ushort expectedProjectKey)
19 | {
20 | // Arrange
21 |
22 | // Act
23 | var actualProjectKey = VbaEncryption.GetProjectKey(projectId);
24 |
25 | // Assert
26 | ClassicAssert.AreEqual(expectedProjectKey, actualProjectKey);
27 | }
28 |
29 | [Test]
30 | [TestCase("{917DED54-440B-4FD1-A5C1-74ACF261E600}", "0705D8E3D8EDDBF1DBF1DBF1DBF1")]
31 | public void ProjectProtectionState_ToEncryptedString_Sample(string projectId, string expectedProtectionState)
32 | {
33 | // Arrange
34 | var state = new ProjectProtectionState(projectId);
35 | state.ProjectProtection = ProjectProtection.None;
36 |
37 | // Act
38 | var actualProtectionState = state.ToEncryptedString(0x07);
39 |
40 | // Assert
41 | ClassicAssert.AreEqual(expectedProtectionState, actualProtectionState);
42 | }
43 |
44 | [Test]
45 | [TestCase("{917DED54-440B-4FD1-A5C1-74ACF261E600}", "0E0CD1ECDFF4E7F5E7F5E7")]
46 | public void ProjectPassword_ToEncryptedString_Sample(string projectId, string expectedProtectionState)
47 | {
48 | // Arrange
49 | const byte seed = 0x0E;
50 | var state = new ProjectPassword(projectId);
51 |
52 | // Act
53 | var actualProtectionState = state.ToEncryptedString(seed);
54 |
55 | // Assert
56 | ClassicAssert.AreEqual(expectedProtectionState, actualProtectionState);
57 | }
58 |
59 | [Test]
60 | [TestCase("{917DED54-440B-4FD1-A5C1-74ACF261E600}", "1517CAF1D6F9D7F9D706")]
61 | public void ProjectVisibilityState_ToEncryptedString_Test(string projectId, string expectedProtectionState)
62 | {
63 | // Arrange
64 | const byte seed = 0x15;
65 | var state = new ProjectVisibilityState(projectId);
66 |
67 | // Act
68 | var actualProtectionState = state.ToEncryptedString(seed);
69 |
70 | // Assert
71 | ClassicAssert.AreEqual(expectedProtectionState, actualProtectionState);
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/tests/VbaCompiler.Tests/VbaSamplesNumberGenerator.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System.Security.Cryptography;
5 |
6 | namespace VbaCompiler.Tests
7 | {
8 | public class VbaSamplesNumberGenerator : RandomNumberGenerator
9 | {
10 | public override void GetBytes(byte[] data)
11 | {
12 | for (int i = 0; i < data.Length; i++)
13 | {
14 | data[i] = 0x07;
15 | }
16 | }
17 |
18 | public override void GetNonZeroBytes(byte[] data)
19 | {
20 | GetBytes(data);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/tests/VbaCompiler.Tests/data/Class.vb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/tests/VbaCompiler.Tests/data/Class.vb
--------------------------------------------------------------------------------
/tests/VbaCompiler.Tests/data/Module.vb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetOfficeFw/vbamc/d2f2439ec6178a40ad23fa23ef31d5bc6234379a/tests/VbaCompiler.Tests/data/Module.vb
--------------------------------------------------------------------------------
/utils/vbad/DirStream.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 | using System.Text;
6 |
7 | namespace vbad
8 | {
9 | public static class DirStream
10 | {
11 | public const short SysKindId = 0x0001;
12 | public const short CompatVersionId = 0x004A;
13 | public const short LcidId = 0x0002;
14 | public const short LcidInvokeId = 0x0014;
15 | public const short CodePageId = 0x0003;
16 | public const short NameId = 0x0004;
17 | public const short DocStringId = 0x0005;
18 | public const short DocStringReserved = 0x0040;
19 | public const short HelpFilePathId = 0x0006;
20 | public const short HelpFilePathReserved = 0x003D;
21 | public const short HelpContextId = 0x0007;
22 | public const short LibFlagsId = 0x0008;
23 | public const short VersionId = 0x0009;
24 | public const short ConstantsId = 0x000C;
25 | public const short ConstantsReserved = 0x003C;
26 |
27 | public const short ReferenceNameId = 0x0016;
28 | public const short ReferenceProjectId = 0x000E;
29 | public const short ReferenceNameReserved = 0x003E;
30 | public const short ReferenceRegisteredId = 0x000D;
31 |
32 | public const short ModuleId = 0x000F;
33 | public const short ModuleCookieId = 0x0013;
34 | public const short ModuleNameId = 0x0019;
35 | public const short ModuleStreamNameId = 0x001A;
36 | public const short ModuleOffsetId = 0x0031;
37 | public const short ModuleTerminatorId = 0x002B;
38 |
39 | public static IEnumerable GetModules(byte[] dir)
40 | {
41 | var reader = new BinaryReader(new MemoryStream(dir));
42 |
43 | // Information record
44 | reader.ReadRecord(SysKindId);
45 | var p = reader.PeekChar();
46 | if (p == CompatVersionId)
47 | {
48 | reader.ReadRecord(CompatVersionId);
49 | }
50 |
51 | reader.ReadRecord(LcidId);
52 | reader.ReadRecord(LcidInvokeId);
53 | reader.ReadRecord(CodePageId);
54 | reader.ReadRecord(NameId);
55 | reader.ReadRecord(DocStringId);
56 | reader.ReadRecord(DocStringReserved);
57 | reader.ReadRecord(HelpFilePathId);
58 | reader.ReadRecord(HelpFilePathReserved);
59 | reader.ReadRecord(HelpContextId);
60 | reader.ReadRecord(LibFlagsId);
61 | reader.ReadProjectVersionRecord(VersionId);
62 | reader.ReadRecord(ConstantsId);
63 | reader.ReadRecord(ConstantsReserved);
64 |
65 | // References record
66 | while (reader.ReadReferences()) { }
67 |
68 | // Modules record
69 | var modules = reader.ReadModules().ToList();
70 |
71 | return modules;
72 | }
73 |
74 | public static void ReadRecord(this BinaryReader reader, short id)
75 | {
76 | var idValue = reader.ReadInt16();
77 | if (idValue != id)
78 | {
79 | throw new InvalidOperationException($"Reading record 0x{idValue:X4} when expected record is 0x{id:X4}");
80 | }
81 |
82 | uint size = reader.ReadUInt32();
83 | reader.ReadBytes((int)size);
84 | }
85 |
86 | public static void ReadProjectVersionRecord(this BinaryReader reader, short id)
87 | {
88 | var idValue = reader.ReadInt16();
89 | if (idValue != id)
90 | {
91 | throw new InvalidOperationException($"Reading record 0x{idValue:X4} when expected record is 0x{id:X4}");
92 | }
93 |
94 | // size is ignored
95 | uint size = reader.ReadUInt32();
96 | reader.ReadBytes(4);
97 | reader.ReadBytes(2);
98 | }
99 |
100 | public static bool ReadReferences(this BinaryReader reader)
101 | {
102 | var idValue = reader.ReadInt16();
103 | if (idValue != ReferenceNameId && idValue != ReferenceProjectId)
104 | {
105 | reader.BaseStream.Position -= 2;
106 | return false;
107 | }
108 |
109 | var nameSize = reader.ReadInt32();
110 | reader.ReadBytes(nameSize);
111 |
112 | _ = reader.ReadInt16();
113 | var nameUnicodeSize = reader.ReadInt32();
114 | reader.ReadBytes(nameUnicodeSize);
115 |
116 | var referenceId = reader.ReadInt16();
117 | switch (referenceId)
118 | {
119 | case ReferenceRegisteredId:
120 | _ = reader.ReadInt32();
121 | var libSize = reader.ReadInt32();
122 | reader.ReadBytes(libSize);
123 | _ = reader.ReadInt32();
124 | _ = reader.ReadInt16();
125 | return true;
126 |
127 | case ReferenceProjectId:
128 | var size = reader.ReadInt32();
129 | reader.ReadBytes(size);
130 | return true;
131 |
132 | default:
133 | throw new InvalidOperationException($"Reading reference 0x{referenceId:X4} when expected reference is 0x{ReferenceRegisteredId:X4}");
134 | }
135 | }
136 |
137 | public static IEnumerable ReadModules(this BinaryReader reader)
138 | {
139 | // Modules record
140 | var idValue = reader.ReadInt16();
141 | if (idValue != ModuleId)
142 | {
143 | throw new InvalidOperationException($"Reading module 0x{idValue:X4} when expected module is 0x{ModuleId:X4}");
144 | }
145 |
146 | var size = reader.ReadUInt32();
147 | var count = reader.ReadUInt16();
148 | var cookieId = reader.ReadInt16();
149 | if (cookieId != ModuleCookieId)
150 | {
151 | throw new InvalidOperationException($"Reading module cookie 0x{cookieId:X4} when expected module cookie is 0x{ModuleCookieId:X4}");
152 | }
153 |
154 | var cookieSize = reader.ReadInt32();
155 | reader.ReadBytes(cookieSize);
156 |
157 | // Module records
158 | for (int i = 0; i < count; i++)
159 | {
160 | var module = reader.ReadModule();
161 | yield return module;
162 | }
163 | }
164 | public static ModuleInfo ReadModule(this BinaryReader reader)
165 | {
166 | // Module record
167 | var module = new ModuleInfo();
168 |
169 | do
170 | {
171 | var id = reader.ReadInt16();
172 | Console.WriteLine($"Module record id: 0x{id:X4}");
173 |
174 | if (id == ModuleStreamNameId)
175 | {
176 | var size = reader.ReadInt32();
177 | var nameBytes = reader.ReadBytes(size);
178 | var name = Encoding.GetEncoding(1252).GetString(nameBytes);
179 |
180 | module.Name = name;
181 | }
182 | else if (id == ModuleOffsetId)
183 | {
184 | var size = reader.ReadInt32();
185 | var offset = reader.ReadUInt32();
186 |
187 | module.Offset = offset;
188 | }
189 | else if (id == ModuleTerminatorId)
190 | {
191 | Console.WriteLine();
192 | _ = reader.ReadInt32();
193 | Console.WriteLine($"Module {module.Name} at offset 0x{module.Offset:X8}");
194 | return module;
195 | }
196 | else
197 | {
198 | var size = reader.ReadInt32();
199 | reader.ReadBytes(size);
200 | }
201 |
202 | } while (true);
203 |
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/utils/vbad/ModuleInfo.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using System;
5 |
6 | namespace vbad
7 | {
8 | public class ModuleInfo
9 | {
10 | public string? Name { get; set; }
11 |
12 | public uint Offset { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/utils/vbad/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Cisco Systems, Inc.
2 | // Licensed under MIT-style license (see LICENSE.txt file).
3 |
4 | using Kavod.Vba.Compression;
5 | using OpenMcdf;
6 | using System.Text;
7 | using vbad;
8 |
9 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
10 |
11 | Console.WriteLine("VBA Project Decompiler");
12 |
13 | var sourcePath = @"d:\dev\github\NetOfficeFw\vbamc\data\powerpoint\Macro2003\ppt\";
14 | var targetPath = @"d:\dev\github\NetOfficeFw\vbamc\data\powerpoint\Macro2003_vbaProject";
15 |
16 | var source = Path.Combine(sourcePath, "vbaProject.bin");
17 |
18 | var vba = new CompoundFile(source);
19 | var root = vba.RootStorage;
20 |
21 | root.VisitEntries(item => ProcessFile(root, "", item), false);
22 | ProcessDirAndModules(root.GetStorage("VBA"));
23 |
24 | Console.WriteLine();
25 | Console.WriteLine($" vbaProject.bin -> {targetPath}");
26 | Console.WriteLine("Project was decompiled.");
27 |
28 | void ProcessFile(CFStorage storage, string directory, CFItem item)
29 | {
30 | if (item.IsStream)
31 | {
32 | var name = item.Name;
33 | var target = Path.Combine(targetPath, directory, name + ".bin");
34 |
35 | var stream = storage.GetStream(name);
36 | var data = stream.GetData();
37 |
38 | if (name == "dir")
39 | {
40 | try
41 | {
42 | data = VbaCompression.Decompress(data);
43 | }
44 | catch (Exception ex)
45 | {
46 | Console.WriteLine(ex.Message);
47 | }
48 | }
49 |
50 | File.WriteAllBytes(target, data);
51 | }
52 | else if (item.IsStorage)
53 | {
54 | var s2 = (CFStorage)item;
55 | var dir = Path.Combine(targetPath, s2.Name);
56 | if (!Directory.Exists(dir))
57 | {
58 | Directory.CreateDirectory(dir);
59 | }
60 |
61 | s2.VisitEntries(item => ProcessFile(s2, s2.Name, item), false);
62 | }
63 | }
64 |
65 |
66 | void ProcessDirAndModules(CFStorage vbaStorage)
67 | {
68 | var dir = vbaStorage.GetStream("dir");
69 | var data = dir.GetData();
70 | data = VbaCompression.Decompress(data);
71 | var target = Path.Combine(targetPath, "vba", "dir.bin");
72 |
73 | File.WriteAllBytes(target, data);
74 |
75 | var modules = DirStream.GetModules(data);
76 | foreach (var module in modules)
77 | {
78 | var name = module.Name;
79 | var targetVbBin = Path.Combine(targetPath, "vba", name + ".bin");
80 | var targetVbText = Path.Combine(targetPath, "vba", name + ".vb");
81 |
82 | var stream = vbaStorage.GetStream(name);
83 | Span dataVb = stream.GetData();
84 | var dataVbBin = dataVb.Slice((int)module.Offset).ToArray();
85 | File.WriteAllBytes(targetVbBin, dataVbBin);
86 |
87 | dataVb = VbaCompression.Decompress(dataVbBin);
88 |
89 | var sourceCode = Encoding.GetEncoding(1252).GetString(dataVb);
90 |
91 | File.WriteAllText(targetVbText, sourceCode);
92 | }
93 | }
--------------------------------------------------------------------------------
/utils/vbad/vbad.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/vbamc.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.1.32407.343
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{A65297C5-32F8-43EF-9443-887A10A11E26}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{A71B7204-5177-40D4-85E9-9191D9D0E871}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "vbad", "utils\vbad\vbad.csproj", "{9DF9B097-B6AD-4804-B77B-368BA556C8B5}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kavod.Vba.Compression", "lib\VbaCompression\src\Kavod.Vba.Compression\Kavod.Vba.Compression.csproj", "{A18B0C04-A7DF-4E2C-A1EF-F5A8F3B3E8EF}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "vbamc", "src\vbamc\vbamc.csproj", "{3CA6032C-FDC1-4C35-AF3F-984BAD460C12}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C879AB47-586B-4229-8D7C-440063943FCA}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VbaCompiler.Tests", "tests\VbaCompiler.Tests\VbaCompiler.Tests.csproj", "{96CB89F7-B342-499A-8B11-0544E8CE084B}"
19 | EndProject
20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VbaCompiler", "src\VbaCompiler\VbaCompiler.csproj", "{EC2B7487-4C21-4118-A405-E4228CFF80B3}"
21 | EndProject
22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VbaCompiler.Benchmark", "tests\VbaCompiler.Benchmark\VbaCompiler.Benchmark.csproj", "{646D544C-BCA4-44CF-9C2A-F69F32B90BAF}"
23 | EndProject
24 | Global
25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
26 | Debug|Any CPU = Debug|Any CPU
27 | Release|Any CPU = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {9DF9B097-B6AD-4804-B77B-368BA556C8B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {9DF9B097-B6AD-4804-B77B-368BA556C8B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {9DF9B097-B6AD-4804-B77B-368BA556C8B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {9DF9B097-B6AD-4804-B77B-368BA556C8B5}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {A18B0C04-A7DF-4E2C-A1EF-F5A8F3B3E8EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {A18B0C04-A7DF-4E2C-A1EF-F5A8F3B3E8EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {A18B0C04-A7DF-4E2C-A1EF-F5A8F3B3E8EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {A18B0C04-A7DF-4E2C-A1EF-F5A8F3B3E8EF}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {3CA6032C-FDC1-4C35-AF3F-984BAD460C12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {3CA6032C-FDC1-4C35-AF3F-984BAD460C12}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {3CA6032C-FDC1-4C35-AF3F-984BAD460C12}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {3CA6032C-FDC1-4C35-AF3F-984BAD460C12}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {96CB89F7-B342-499A-8B11-0544E8CE084B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {96CB89F7-B342-499A-8B11-0544E8CE084B}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {96CB89F7-B342-499A-8B11-0544E8CE084B}.Release|Any CPU.ActiveCfg = Release|Any CPU
45 | {96CB89F7-B342-499A-8B11-0544E8CE084B}.Release|Any CPU.Build.0 = Release|Any CPU
46 | {EC2B7487-4C21-4118-A405-E4228CFF80B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {EC2B7487-4C21-4118-A405-E4228CFF80B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {EC2B7487-4C21-4118-A405-E4228CFF80B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {EC2B7487-4C21-4118-A405-E4228CFF80B3}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {646D544C-BCA4-44CF-9C2A-F69F32B90BAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 | {646D544C-BCA4-44CF-9C2A-F69F32B90BAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 | {646D544C-BCA4-44CF-9C2A-F69F32B90BAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {646D544C-BCA4-44CF-9C2A-F69F32B90BAF}.Release|Any CPU.Build.0 = Release|Any CPU
54 | EndGlobalSection
55 | GlobalSection(SolutionProperties) = preSolution
56 | HideSolutionNode = FALSE
57 | EndGlobalSection
58 | GlobalSection(NestedProjects) = preSolution
59 | {9DF9B097-B6AD-4804-B77B-368BA556C8B5} = {A71B7204-5177-40D4-85E9-9191D9D0E871}
60 | {A18B0C04-A7DF-4E2C-A1EF-F5A8F3B3E8EF} = {A65297C5-32F8-43EF-9443-887A10A11E26}
61 | {96CB89F7-B342-499A-8B11-0544E8CE084B} = {C879AB47-586B-4229-8D7C-440063943FCA}
62 | {646D544C-BCA4-44CF-9C2A-F69F32B90BAF} = {C879AB47-586B-4229-8D7C-440063943FCA}
63 | EndGlobalSection
64 | GlobalSection(ExtensibilityGlobals) = postSolution
65 | SolutionGuid = {8451A57F-A750-4C84-BFDE-48D3CD3D5F33}
66 | EndGlobalSection
67 | EndGlobal
68 |
--------------------------------------------------------------------------------