Reputation: 19117
Is there any way, in either VBA or C# code, to get a list of the existing macros defined in a workbook?
Ideally, this list would have a method definition signatures, but just getting a list of the available macros would be great.
Is this possible?
Upvotes: 3
Views: 2344
Reputation: 177
That code is make new sheet in book and print table with columns about all book's macro_name, link to run macro, link to open macro in ide, name of corresponding module, and sort table by module and name, if sheet is already exist and visible then it hide it, if is not then it add of make visible and print all that describe above:
Sub ListMacrosWithSortedLinks()
Dim wb As Workbook
Dim ws As Worksheet
Dim rowNum As Long
Dim macroName As String
Dim moduleComp As Object
Dim lineText As String
Dim btn As Button
' Set the workbook
Set wb = ThisWorkbook
' Add "MACROS" sheet if it doesn't exist
On Error Resume Next
Set ws = wb.Sheets("MACROS")
On Error GoTo 0
If ws Is Nothing Then
Set ws = wb.Sheets.Add(After:=wb.Sheets(wb.Sheets.Count))
ws.name = "MACROS"
End If
' Hide "MACROS" sheet if it was visible
If ws.Visible = xlSheetVisible Then
ws.Visible = xlSheetVeryHidden
Exit Sub
End If
' Make "MACROS" sheet visible if not visible
If ws.Visible = xlSheetHidden Or ws.Visible = xlSheetVeryHidden Then
ws.Visible = xlSheetVisible
ws.Select ' Select the sheet
End If
' Clear previous data and buttons in columns A, B, C, and D
ws.Cells.ClearContents
ws.Buttons.Delete
' Write headers and make them bold
With ws.Range("A1:D1")
.Value = Array("Macro Name", "Run Macro", "Open VBA IDE", "Module Name")
.Font.Bold = True
End With
' Initialize row number for writing
rowNum = 2
' Loop through all modules in the workbook
For Each moduleComp In wb.VBProject.VBComponents
If moduleComp.Type = 1 Then ' Check if it's a module
For i = 1 To moduleComp.codeModule.CountOfLines
lineText = moduleComp.codeModule.Lines(i, 1)
If InStr(1, lineText, "Sub ") = 1 Or InStr(1, lineText, "Private Sub ") = 1 Then
macroName = Trim(Mid(lineText, InStr(1, lineText, "Sub ") + 4))
macroName = Left(macroName, InStr(1, macroName, "(") - 1)
' Apply formatting to the cell before adding the hyperlink
ws.Cells(rowNum, 2).Font.color = RGB(192, 192, 192) ' Silver color
' Create a hyperlink-styled link to run the macro
ws.Hyperlinks.Add Anchor:=ws.Cells(rowNum, 2), _
Address:="", SubAddress:=moduleComp.name & "." & macroName, _
TextToDisplay:="Run Macro"
' Create a hyperlink to open VBA IDE
ws.Hyperlinks.Add Anchor:=ws.Cells(rowNum, 3), _
Address:="", SubAddress:=moduleComp.name & "." & macroName, _
TextToDisplay:="Open VBA IDE"
' Write macro information to worksheet
ws.Cells(rowNum, 1).Value = macroName
ws.Cells(rowNum, 4).Value = moduleComp.name
' Increment the row number
rowNum = rowNum + 1
End If
Next i
End If
Next moduleComp
' Sort the data by Module Name (col4) ascending, then by Macro Name (col1)
ws.Sort.SortFields.Clear
ws.Sort.SortFields.Add Key:=ws.Range("D2:D" & rowNum - 1), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
ws.Sort.SortFields.Add Key:=ws.Range("A2:A" & rowNum - 1), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With ws.Sort
.SetRange ws.Range("A1:D" & rowNum - 1)
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
Upvotes: 0
Reputation: 4327
Building on Martin's answer, after you trust access to the VBP, you can use this set of code to get an array of all the public subroutines in an Excel workbook's VB Project. You can modify it to only include subs, or just funcs, or just private or just public...
Private Sub TryGetArrayOfDecs()
Dim Decs() As String
DumpProcedureDecsToArray Decs
End Sub
Public Function DumpProcedureDecsToArray(ByRef Result() As String, Optional InDoc As Excel.Workbook) As Boolean
Dim VBProj As Object
Dim VBComp As Object
Dim VBMod As Object
If InDoc Is Nothing Then Set InDoc = ThisWorkbook
ReDim Result(1 To 1500, 1 To 4)
DumpProcedureDecsToArray = True
On Error GoTo PROC_ERR
Set VBProj = InDoc.VBProject
Dim FuncNum As Long
Dim FuncDec As String
For Each VBComp In VBProj.vbcomponents
Set VBMod = VBComp.CodeModule
For i = 1 To VBMod.countoflines
If IsSubroutineDeclaration(VBMod.Lines(i, 1)) Then
FuncDec = RemoveBlanksAndDecsFromSubDec(RemoveAsVariant(VBMod.Lines(i, 1)))
If LCase(Left(VBMod.Lines(i, 1), Len("private"))) <> "private" Then
FuncNum = FuncNum + 1
Result(FuncNum, 1) = FindToLeftOfString(InDoc.Name, ".") '
Result(FuncNum, 2) = VBMod.Name
Result(FuncNum, 3) = GetSubName(FuncDec)
Result(FuncNum, 4) = VBProj.Name
End If
End If
Next i
Next VBComp
PROC_END:
Exit Function
PROC_ERR:
GoTo PROC_END
End Function
Private Function RemoveCharFromLeftOfString(TheString As String, RemoveChar As String) As String
Dim Result As String
Result = TheString
While LCase(Left(Result, Len(RemoveChar))) = LCase(RemoveChar)
Result = Right(Result, Len(Result) - Len(RemoveChar))
Wend
RemoveCharFromLeftOfString = Result
End Function
Private Function RemoveBlanksAndDecsFromSubDec(TheLine As String) As String
Dim Result As String
Result = TheLine
Result = RemoveCharFromLeftOfString(Result, " ")
Result = RemoveCharFromLeftOfString(Result, " ")
Result = RemoveCharFromLeftOfString(Result, "Public ")
Result = RemoveCharFromLeftOfString(Result, "Private ")
Result = RemoveCharFromLeftOfString(Result, " ")
RemoveBlanksAndDecsFromSubDec = Result
End Function
Private Function RemoveAsVariant(TheLine As String) As String
Dim Result As String
Result = TheLine
Result = Replace(Result, "As Variant", "")
Result = Replace(Result, "As String", "")
Result = Replace(Result, "Function", "")
If InStr(1, Result, "( ") = 0 Then
Result = Replace(Result, "(", "( ")
End If
RemoveAsVariant = Result
End Function
Private Function IsSubroutineDeclaration(TheLine As String) As Boolean
If LCase(Left(RemoveBlanksAndDecsFromSubDec(TheLine), Len("Function "))) = "function " Or LCase(Left(RemoveBlanksAndDecsFromSubDec(TheLine), Len("sub "))) = "sub " Then
IsSubroutineDeclaration = True
End If
End Function
Private Function GetSubName(DecLine As String) As String
GetSubName = FindToRightOfString(FindToLeftOfString(DecLine, "("), " ")
End Function
Function FindToLeftOfString(FullString As String, ToFind As String) As String
If FullString = "" Then Exit Function
Dim Result As String, ToFindPos As Integer
ToFindPos = InStr(1, FullString, ToFind, vbTextCompare)
If ToFindPos > 0 Then
Result = Left(FullString, ToFindPos - 1)
Else
Result = FullString
End If
FindToLeftOfString = Result
End Function
Function FindToRightOfString(FullString As String, ToFind As String) As String
If FullString = "" Then Exit Function
Dim Result As String, ToFindPos As Integer
ToFindPos = InStr(1, FullString, ToFind, vbTextCompare)
Result = Right(FullString, Len(FullString) - ToFindPos + 1 - Len(ToFind))
If ToFindPos > 0 Then
FindToRightOfString = Result
Else
FindToRightOfString = FullString
End If
End Function
Upvotes: 1
Reputation: 6032
I haven't done vba for Excel in a long time, but if I remember well, the object model for the code was inaccessible through scripting.
When you try to access it, you receive the following error.
Run-time error '1004': Programmatic access to Visual Basic Project is not trusted
Try:
Tools | Macro | Security |Trusted Publisher Tab [x] Trust access to Visual Basic Project
Now that you have access to the VB IDE, you could probably export the modules and make a text search in them, using vba / c#, using regular expressions to find sub and function declarations, then delete the exported modules.
I'm not sure if there is an other way to do this, but this should work.
You can take a look the following link, to get started with exporting the modules. http://www.developersdex.com/vb/message.asp?p=2677&ID=%3C4FCD0AE9-5DCB-4A96-8B3C-F19C63CD3635%40microsoft.com%3E
This is where I got the information about giving thrusted access to the VB IDE.
Upvotes: 1