10

Take this code:

With ActiveSheet.Shapes.AddShape(msoShapeRectangle, x, y, w, h).TextFrame
  .Parent.Line.Visible = False
  .Parent.Fill.ForeColor.RGB = RGB(r, g, b)
End With

Is there any VBA way to "execute" or "evaluate" like can be done in perl/python/... such that the text .Parent.Line.Visible can be drawn from a variable (or cell value), rather than hard coded?

ParentLine = ".Parent.Line.Visible"
ParentLineValue = "False"

With ActiveSheet.Shapes.AddShape(msoShapeRectangle, x, y, w, h).TextFrame
  **eval**(ParentLine & "=" & ParentLineValue)
  .Parent.Fill.ForeColor.RGB = RGB(r, g, b)
End With

EDIT: I found MSDN information for Access that mentions Eval, but when I execute my code it says "Undefined Sub or Function", pointing at Eval (Excel does not seem to know this function).

EDIT 2: Found the definitive (negative) answer on SO.

EDIT 3: Seems like there is an answer after all, as I am not after a general solution for arbitrary code execution. Thanks to GSerg for helping with using CallByName.

Community
  • 1
  • 1
asoundmove
  • 1,292
  • 3
  • 14
  • 28
  • I'm curious as to when one would ever want to do this? – Jean-François Corbett Apr 18 '11 at 18:01
  • I simply want to parameterise a macro and not force users to read VBA, just play with their spreadsheet. I am aware of the security implications, but that's for limited use only. Might want to set different properties, alternatively to that one. – asoundmove Apr 18 '11 at 18:12
  • `ScriptControl` ActiveX can help you to evaluate string containing any `Application`'s member, check [this answer](http://stackoverflow.com/a/33507870/2165759). – omegastripes Mar 31 '16 at 19:01

3 Answers3

12

Solution 1.

Use CallByName.

Option Explicit

Private Type Callable
  o As Object
  p As String
End Type

Public Sub SetProperty(ByVal path As String, ByVal Value As Variant, Optional ByVal RootObject As Object = Nothing)
  With GetObjectFromPath(RootObject, path)
    If IsObject(Value) Then
      CallByName .o, .p, VbSet, Value
    Else
      CallByName .o, .p, VbLet, Value
    End If
  End With
End Sub

Public Function GetProperty(ByVal path As String, Optional ByVal RootObject As Object = Nothing) As Variant
  With GetObjectFromPath(RootObject, path)
    GetProperty = CallByName(.o, .p, VbGet)
  End With
End Function

Public Function GetPropertyAsObject(ByVal path As String, Optional ByVal RootObject As Object = Nothing) As Object
  With GetObjectFromPath(RootObject, path)
    Set GetPropertyAsObject = CallByName(.o, .p, VbGet)
  End With
End Function


Private Function GetObjectFromPath(ByVal RootObject As Object, ByVal path As String) As Callable
  'Returns the object that the last .property belongs to
  Dim s() As String
  Dim i As Long

  If RootObject Is Nothing Then Set RootObject = Application

  Set GetObjectFromPath.o = RootObject

  s = Split(path, ".")

  For i = LBound(s) To UBound(s) - 1
    If Len(s(i)) > 0 Then
      Set GetObjectFromPath.o = CallByName(GetObjectFromPath.o, s(i), VbGet)
    End If
  Next

  GetObjectFromPath.p = s(UBound(s))
End Function

Usage:

? getproperty("activecell.interior.color")
16777215 

SetProperty "activecell.interior.color", vbYellow
'Sets yellow background

? getproperty("names.count", application.ActiveWorkbook)
0 

? getproperty("names.count", GetPropertyAsObject("application.activeworkbook"))
0

Solution 2.

Dynamically add code.
Don't do this. It's wrong and it requires having that "Allow access to VB project" tick set.

Add a reference to Microsoft Visual Basic for Applications Extensibility X.X.

Create a module called ModuleForCrap.

Add a dynamically constructed sub/function:

ThisWorkbook.VBProject.VBComponents("ModuleForCrap").CodeModule.AddFromString _
"function foobar() as long" & vbNewLine & _
"foobar = 42" & vbNewLine & _
"end function"`

Call it:

msgbox application.run("ModuleForCrap.foobar")

Delete it:

With ThisWorkbook.VBProject.VBComponents("ModuleForCrap").CodeModule
  .DeleteLines .ProcStartLine("foobar", vbext_pk_Proc), .ProcCountLines("foobar", vbext_pk_Proc)
End With
GSerg
  • 76,472
  • 17
  • 159
  • 346
  • great. It works with `SetProperty "interior.color", vbYellow, Data.Cells(1, 5)`, though for some reason it does not like `SetProperty "Data.Cells(1,5).interior.color", vbYellow` - but that does me fine. – asoundmove Apr 18 '11 at 20:43
  • 2
    @asoundmove: Yes, only dots are supported inside the path string. If you were to support embedded parameters, you'd end up writing a comprehensive parser, at which point it'd be easier to use the second approach. – GSerg Apr 18 '11 at 21:12
1

You could try looking at CallByName, but I don't think it's going to do what you want (at least, not easily if you're going to want to evaluate multi-dot object/property references).

lennon310
  • 12,503
  • 11
  • 43
  • 61
Tim Williams
  • 154,628
  • 8
  • 97
  • 125
0

False evaluates to Zero. You can construct an integer variable to equate to zero and have it turn out the same way as False.

Harry
  • 256
  • 1
  • 2
  • 13
  • fine but my problem is a lot more basic than that. I edited my question to refine my ask. I realise that this is more of a support question, but I can't find the relevant information anywhere. – asoundmove Apr 18 '11 at 18:31