如何通过System.IO.Packaging为XLSX/XLSM文件添加宏文件?
Got it, since you’ve already nailed the manual process, translating that into code with System.IO.Packaging is straightforward. We just need to replicate the two key steps you did manually: adding the vbaProject.bin file to the package and updating the relationship/config files. Let’s walk through each part with code examples.
Step 1: Open the Excel Package
First, make sure you’re working with an .xlsm file (macro-enabled Excel files require this extension). Open the package in read-write mode:
using System.IO; using System.IO.Packaging; using System.Xml.Linq; // Path to your target macro-enabled Excel file string excelFilePath = @"C:\path\to\your\file.xlsm"; // Path to your pre-created vbaProject.bin string vbaBinPath = @"C:\path\to\your\vbaProject.bin"; using (Package package = Package.Open(excelFilePath, FileMode.Open, FileAccess.ReadWrite)) { // We'll add all our steps inside this using block }
Step 2: Add the vbaProject.bin Part to the Package
The vbaProject.bin needs to live as a top-level part in the package. Create the part and write your pre-extracted binary data to it:
// Define the URI for the VBA project part (must be top-level) Uri vbaPartUri = new Uri("/vbaProject.bin", UriKind.Relative); // Skip if the part already exists to avoid duplicates if (!package.PartExists(vbaPartUri)) { // Create the part with the required VBA content type PackagePart vbaPart = package.CreatePart( vbaPartUri, "application/vnd.ms-office.vbaProject", CompressionOption.Normal); // Write the vbaProject.bin data to the package part using (FileStream fs = new FileStream(vbaBinPath, FileMode.Open, FileAccess.Read)) using (Stream partStream = vbaPart.GetStream()) { fs.CopyTo(partStream); } }
Step 3: Update [Content_Types].xml
We need to register the .bin extension with the correct content type so Excel recognizes the VBA project. Load the content types file and add the missing entry if it doesn’t exist:
Uri contentTypesUri = PackUriHelper.GetContentTypeUri(); PackagePart contentTypesPart = package.GetPart(contentTypesUri); // Load the content types XML into an XDocument for easy editing XDocument contentTypesDoc; using (StreamReader sr = new StreamReader(contentTypesPart.GetStream())) { contentTypesDoc = XDocument.Load(sr); } XNamespace ctNs = "http://schemas.openxmlformats.org/package/2006/content-types"; // Check if the .bin content type is already registered bool hasBinContentType = contentTypesDoc.Root.Elements(ctNs + "Default") .Any(d => (string)d.Attribute("Extension") == "bin"); if (!hasBinContentType) { contentTypesDoc.Root.Add( new XElement(ctNs + "Default", new XAttribute("Extension", "bin"), new XAttribute("ContentType", "application/vnd.ms-office.vbaProject"))); // Save the updated content types back to the package using (StreamWriter sw = new StreamWriter(contentTypesPart.GetStream(FileMode.Create))) { contentTypesDoc.Save(sw); } }
Step 4: Update the Root Relationships (.rels)
Finally, add a relationship from the package root to the vbaProject.bin part so Excel knows where to find the macro project:
Uri rootRelsUri = PackUriHelper.GetRelationshipsUri(new Uri("/", UriKind.Relative)); PackageRelationshipCollection rootRels = package.GetRelationships(); // Check if the VBA relationship already exists bool hasVbaRel = rootRels.Any(r => r.RelationshipType == "http://schemas.microsoft.com/office/2006/relationships/vbaProject"); if (!hasVbaRel) { // Add the relationship with a unique ID (adjust rId10 if needed to avoid conflicts) package.CreateRelationship( vbaPartUri, TargetMode.Internal, "http://schemas.microsoft.com/office/2006/relationships/vbaProject", "rId10"); }
Step 5: Save and Close
The using block will automatically save all changes and close the package once execution exits the block.
Quick Tips
- Always test the output file in Excel—you may need to enable macros via Excel’s Trust Center settings to run them.
- If converting a regular
.xlsxfile to macro-enabled, rename it to.xlsmbefore modifying the package.
内容的提问来源于stack exchange,提问作者Maury Markowitz

