OpenXML SDK Inject VBA into excel workbook

后端 未结 3 827
梦谈多话
梦谈多话 2021-02-06 06:52

I can successfully inject a piece of VBA code into a generated excel workbook, but what I am trying to do is use the Workbook_Open() event so the VBA code executes when the file

相关标签:
3条回答
  • 2021-02-06 07:18

    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";
        }
    }
    
    0 讨论(0)
  • 2021-02-06 07:21

    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.

    0 讨论(0)
  • 2021-02-06 07:27

    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.

    0 讨论(0)
提交回复
热议问题