OpenXML SDK Inject VBA into excel workbook

喜夏-厌秋 提交于 2019-12-03 07:32:52
Lentonator

Based on my research there isn't a way to insert the project part data in a format that you can manipulate in C#. In the OpenXML format, the VBA project is still stored in a binary format. However, copying the VbaProjectPart from one Excel document into another should work. As a result, you'd have to determine what you wanted the project part to say in advance.

If you are OK with this, then you can add the following code to a template Excel file in the 'ThisWorkbook' Microsoft Excel Object, along with the appropriate Macro code:

Private Sub Workbook_Open()
    Run "Module1.SomeMacroName()"
End Sub

To copy the VbaProjectPart object from one file to the other, you would use code like this:

public static void InsertVbaPart()
{
    using(SpreadsheetDocument ssDoc = SpreadsheetDocument.Open("file1.xlsm", false))
    {
        WorkbookPart wbPart = ssDoc.WorkbookPart;
        MemoryStream ms;
        CopyStream(ssDoc.WorkbookPart.VbaProjectPart.GetStream(), ms);

        using(SpreadsheetDocument ssDoc2 = SpreadsheetDocument.Open("file2.xlsm", true))
        {
            Stream stream = ssDoc2.WorkbookPart.VbaProjectPart.GetStream();
            ms.WriteTo(stream);
        }
    }
}

public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[short.MaxValue + 1];
    while (true)
    {
        int read = input.Read(buffer, 0, buffer.Length);
        if (read <= 0)
            return;
        output.Write(buffer, 0, read);
    }
}

Hope that helps.

ZlotaMoneta

You need to specify "codeName" attribute in the "xl/workbook..xml" object After feeding the VbaProjectPart with macro. Add this code:

var workbookPr = spreadsheetDocument.WorkbookPart.Workbook.Descendants<WorkbookProperties>().FirstOrDefault();
workbookPr.CodeName = "ThisWorkBook";

After opening the file everything should work now.

So, to add macro you need to:

  1. Change document type to macro enabled

  2. Add VbaProjectPart and feed it with earlier created macro

  3. Add workbookPr codeName attr in xl/workbook..xml with value "ThisWorkBook"

  4. Save as with .xlsm ext.

I found that the other answers still resulted in the duplicate "Worksheet" object. I used a similar solution to what @ZlotaMoneta said, but with a different syntax found here:

List<VbaProjectPart> newParts = new List<VbaProjectPart>();
using (var originalDocument = SpreadsheetDocument.Open("file1.xlsm"), false))
{
    newParts = originalDocument.WorkbookPart.GetPartsOfType<VbaProjectPart>().ToList();

    using (var document = SpreadsheetDocument.Open("file2.xlsm", true))
    {
        document.WorkbookPart.DeleteParts(document.WorkbookPart.GetPartsOfType<VbaProjectPart>());

        foreach (var part in newParts)
        {
            VbaProjectPart vbaProjectPart = document.WorkbookPart.AddNewPart<VbaProjectPart>();
            using (Stream data = part.GetStream())
            {
                vbaProjectPart.FeedData(data);
            }                    
        }

        //Note this prevents the duplicate worksheet issue
        spreadsheetDocument.WorkbookPart.Workbook.WorkbookProperties.CodeName = "ThisWorkbook";
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!