Search code examples
c#excelcsvxlsm

Perform excel macro on a csv file from C#


I need to perform an existing Excel Macro (written in VB, can be copied from Excel using the Macro editor) on an existing csv file using C#. I already have working code that I can use to perform the Macro on an xlsm file, which looks like this:

using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace MacroBuddy
{
    public class test
    {
        public static void go_Macro()
        {
            object oMissing = System.Reflection.Missing.Value;

            //create new Excel application instance
            Excel.Application oExcel = new Excel.Application();

            oExcel.Visible = true;

            Excel.Workbooks oBooks = oExcel.Workbooks;
            Excel._Workbook oBook = null;

            string path = @"C:\Users\user\Desktop\test.csv";

            //open file located at path
            oBook = oBooks.Open(path, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing);

            //run Macro by referencing file and the name of the Macro
            RunMacro(oExcel, new Object[] { "test.xlsm!TestMacro" });

            //save and close workbook
            oBook.Save();
            oBook.Close(false, oMissing, oMissing);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(oBook);
            oBook = null;
            System.Runtime.InteropServices.Marshal.ReleaseComObject(oBooks);
            oBooks = null;
            oExcel.Quit();
            System.Runtime.InteropServices.Marshal.ReleaseComObject(oExcel);
            oExcel = null;
            GC.Collect();
        }

        private static void RunMacro(object oApp, object[] oRunArgs)
        { oApp.GetType().InvokeMember("Run", System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.InvokeMethod, null, oApp, oRunArgs); }

        static void Main()
        { go_Macro(); }
    }
}

However, it does not work if the file specified is a csv file. So I need help make similar code work on a csv file, or an automated process to convert the csv file to an xlsm file from C#.

Also, it would be helpful to be able to take the VB macro code as a string and be able to run a macro using a method that would take the string as an argument or some similar process.


Solution

  • Instead of writing to a csv in the first place, I decided to write to an .xlsx file with multiple sheets using something like this:

    public class test
    {
        object missing = Type.Missing;
        public test()
        {
            Excel.Application XL = new Excel.Application();
            oXL.Visible = false;
            Excel.Workbook WB = XL.Workbooks.Add(missing);
            Excel.Worksheet Sheet = WB.ActiveSheet as Excel.Worksheet;
            oSheet.Name = "First sheet";
            oSheet.Cells[1, 1] = "Written on first sheet";
            Excel.Worksheet Sheet2 = WB.Sheets.Add(missing, missing, 1, missing) 
                        as Excel.Worksheet;
            Sheet2.Name = "Second sheet";
            Sheet2.Cells[1, 1] = "Written on second sheet";
            string fileName = @"C:\temp\SoSample.xlsx";
            oWB.SaveAs(fileName, Excel.XlFileFormat.xlOpenXMLWorkbook,
                missing, missing, missing, missing,
                Excel.XlSaveAsAccessMode.xlNoChange,
                missing, missing, missing, missing, missing);
            oWB.Close(missing, missing, missing);
            oXL.UserControl = true;
            oXL.Quit();
        }
    }
    

    And I then created the with the workbook with the following code:

    private void button1_Click(object sender, System.EventArgs e)
    {
             Excel.Application oExcel;
             Excel.Workbook oBook;
             VBIDE.VBComponent oModule;
             Office.CommandBar oCommandBar;
             Office.CommandBarButton oCommandBarButton;
             String sCode;
             Object oMissing = System.Reflection.Missing.Value;
             // Create an instance of Excel.
             oExcel = new Excel.Application();
    
             // Add a workbook.
             oBook = oExcel.Workbooks.Add(@"C:\Users\user\Downloads\test.xlsm");
    
             // Create a new VBA code module.
             oModule = oBook.VBProject.VBComponents.Add(VBIDE.vbext_ComponentType.vbext_ct_StdModule);
    
             sCode =
             //paste in your macro here, with each line followed by a new line
             "Sub TestMacro()\r\n" +
             "Columns(\"D:D\").Select\r\n" +
             "Selection.Copy\r\n" +
             "Columns(\"F:F\").Select\r\n" +
             "ActiveSheet.Paste\r\n" +
             "Application.CutCopyMode = False\r\n" +
             "ActiveSheet.Range(\"$F$1:$F$542\").RemoveDuplicates Columns:=1, Header:=xlNo\r\n" +
             "Range(\"F1\").Select\r\n" +
             "ActiveCell.FormulaR1C1 = \"Unique Query\"\r\n" +
             "Range(\"F2\").Select\r\n" +
             "End Sub";
             // Add the VBA macro to the new code module.
             oModule.CodeModule.AddFromString(sCode);
    
    
    
             // Make Excel visible to the user.
             oExcel.Visible = true;
             // Set the UserControl property so Excel won't shut down.
             oExcel.UserControl = true;
    
             // Release the variables.
             oModule = null;
             oBook = null;
             oExcel = null;             
             // Collect garbage.
             GC.Collect();
    }
    

    I was then able to run the macro just created with the code I originally posted.