# Background #
I am working on a VBA paradigm for modifying the Excel environment from a UDF. I call this **"sudo mode"**, and I leverage [`Application.Run()`][1] to invoke a delegate procedure, like `RunSudo()` in my example.
> ```vba
> ' Delegate to sudo mode.
> ...
> Application.Run GetSudoCall()
> ' ^^^^^^^^^^^^^
> ' "...RunSudo()"
> ```
By passing the entire call `"RunSudo()"` rather than the name `"RunSudo"`, we "trick" VBA into "losing track" of the procedure, and we thus circumvent the ["ban"][4] on UDFs
> A user-defined function called by a formula in a worksheet cell cannot change the environment of Microsoft Excel.
which forbids them to modify the environment (beyond the cell [`Application.Caller`][2]).
The arguments for `RunSudo()` are cached in `Sudo_argList`
> ```vba
> ' Cache the arguments for sudo mode.
> Sudo_argList.Add rng, "rng"
> Sudo_argList.Add val, "val"
> ```
a [`Collection`][3] object at the modular level
> ```vba
> ' The parameteric cache for sudo mode.
> ...
> Private Sudo_argList As New Collection
> ```
such that `Application.Run()` need only invoke an empty call `"RunSudo()"`, without awkwardly splicing an arbitrary set of arguments as `String`s.
# Code #
I demonstrate this paradigm here, in the **`Demo`** module, with a UDF aptly named `Range_SetValue()`. In an Excel formula
```swift
= Range_SetValue(X2:Z8, 5)
```
this UDF will overwrite the cells `X2:Z8` with the number `5`, whenever the formula is calculated.
## `Demo` ##
```vba
' #######################
' ## Modular Constants ##
' #######################
' The name of the module.
Private Const MOD_NAME As String = "Demo"
' The name of the sudo procedure.
Private Const SUDO_NAME As String = "RunSudo"
' ###############
' ## Sudo Mode ##
' ###############
' The parameteric cache for sudo mode.
Private Sudo_isActive As Boolean
Private Sudo_argList As New Collection
' Activate sudo mode.
Private Sub ActivateSudo()
' Record activation status.
Sudo_isActive = True
End Sub
' Deactivate sudo mode (and clear cache).
Private Sub DeactivateSudo()
' Clear argument cache.
Set Sudo_argList = Nothing
' Record deactivation status.
Sudo_isActive = False
End Sub
' Generate the call to run sudo mode.
Private Function GetSudoCall() As String
GetSudoCall = QualifyCall(SUDO_NAME, MOD_NAME, ThisWorkbook.Name)
End Function
' Modify the range value(s) in sudo mode.
Private Sub RunSudo()
' Catch any errors for housekeeping.
On Error GoTo Catch
' Only perform task if sudo mode is properly activated.
If Sudo_isActive Then
' Modify the range values.
Sudo_argList("rng").Value = Sudo_argList("val")
' Deactivate sudo mode to prevent redundant calls.
DeactivateSudo
End If
Exit Sub
' Housekeeping:
Catch:
' Prevent redundant calls...
DeactivateSudo
' ...before passing on the error.
Err_Raise
End Sub
' ##############
' ## User API ##
' ##############
' Set the value(s) for a range of cells, and report the outcome.
Public Function Range_SetValue( _
ByRef rng As Range, _
ByRef val As Variant _
) As Boolean
On Error GoTo Fail
' Cache the arguments for sudo mode.
Sudo_argList.Add rng, "rng"
Sudo_argList.Add val, "val"
' Delegate to sudo mode.
ActivateSudo
Application.Run GetSudoCall()
' Report success.
Range_SetValue = True
Exit Function
Fail:
Debug.Print Err.Description
' Report failure.
Range_SetValue = False
End Function
' #################
' ## Diagnostics ##
' #################
' Raise an (objective) error.
Private Sub Err_Raise(Optional ByRef e As ErrObject = Nothing)
' Default to the current error in VBA.
If e Is Nothing Then
Set e = VBA.Err
End If
' Raise that error.
VBA.Err.Raise _
Number:=e.Number, _
Source:=e.Source, _
Description:=e.Description, _
HelpFile:=e.HelpFile, _
HelpContext:=e.HelpContext
End Sub
' #############
' ## Helpers ##
' #############
' Format a fully qualified call to a procedure.
Private Function QualifyCall( _
ByRef procName As String, _
Optional ByRef modName As String = Empty, _
Optional ByRef wbName As String = Empty _
) As String
QualifyCall = procName & "(" & ")"
If modName <> Empty Then
QualifyCall = modName & "." & QualifyCall
Else
Exit Function
End If
If wbName <> Empty Then
QualifyCall = FormatWbRef(wbName) & "!" & QualifyCall
End If
End Function
' Format a workbook reference.
Private Function FormatWbRef(ByRef wbName As String) As String
FormatWbRef = "'" & VBA.Replace(wbName, "'", "''") & "'"
End Function
```
# Concern #
My biggest challenge in using `Application.Run()` is in avoiding any nomenclatural clashes with synonyms: procedures from other modules, and defined [`Name`][5]s throughout Excel. I have tried many approaches, but all have their weaknesses:
## Renaming as `A1()` ##
Like anything that resembles a cell address, the name `A1` is [illegal][6] for defined `Name`s. In renaming `RunSudo()` as `A1()`, we ensure that
```vba
Application.Run "A1()"
```
will not conflict with any such `Name`s. However, it might plausibly clash with some `A1()` procedure from some other module.
## Prefixing with `Demo.` ##
Since the `.` character is [illegal][7] in names for VBA objects, the qualifying call `Demo.RunSudo()` or `Demo.A1()` will be unambiguous among synonyms like `Mod2.RunSudo()` and `Mod2.A1()` from other modules. However, names like `Demo.RunSudo()` — and even `Demo.A1()` — actually _are_ legal as `Name`s, so we expose ourselves once again to _those_ clashes.
## Randomizing a Suffix ##
We could play the odds and suffix `RunSudo()` with a random sequence of alphanumerics
```vba
RunSudo_2398hrfj092389h2434...jf9ge980h5675h6e()
' |------------------...---------------|
' 240 characters
```
such that the total length of the `String`
```vba
"Demo.RunSudo_2398hrfj092389h2434...jf9ge980h5675h6e()"
```
meets the [limit of `255`][8] characters on the `Macro` argument to `Application.Run()`. While this essentially rules out a _coincidental_ clash, malicious interference is easily possible via a simple copy-paste, since `Name`s [may also][6] have `255` characters. Furthermore, the length heavily encumbers normal usage, and it essentially rules out many practical applications (like hyperlinks, etc.).
## Recursion on `Range_SetValue()` ##
It is possible to have `Range_SetValue()` perform recursion, and call _itself_ via `Application.Run()`:
```vba
' ...
If Sudo_isActive Then
' ...
RunSudo
' ...
Else
' ...
ActivateSudo
Application.Run "Range_SetValue()"
' ...
End If
' ...
```
But this would require that all arguments to `Range_SetValue()` be `Optional`, to preserve the syntactic validity of the call `"Range_SetValue()"`. Furthermore, it impacts security in a number of unpleasant ways, since the user may now access `RunSudo()` far more directly; the specifics are complex in my use case, so please take my word for it.
## Full Qualification ##
My heart nearly leapt for joy when I realized one may qualify `RunSudo()` like so:
```vba
'Workbook''s Name.ext'!Demo.RunSudo()
```
Since the `!` character is [illegal][6] in `Name`s, this call would never clash with them, and the qualification automatically disambiguates it from synonyms in other modules and workbooks.
Then I realized with despair that a workbook may have a rather long name, and that every `'` must be escaped by another `'`. Thus, the entire call may exceed `255` characters when the workbook's name is long or riddled with `'`s, a fact I confirmed (miserably) by testing.
# Question #
How might I rigorously stabilize this approach against clashes on the name `RunSudo`, or more generally on any `Sub` like `Foo()` in one of several modules within one of several workbooks?
[1]: https://learn.microsoft.com/en-us/office/vba/api/excel.application.run
[2]: https://learn.microsoft.com/en-us/office/vba/api/excel.application.caller
[3]: https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/collection-object
[4]: https://support.microsoft.com/en-us/topic/description-of-limitations-of-custom-functions-in-excel-f2f0ce5d-8ea5-6ce7-fddc-79d36192b7a1
[5]: https://learn.microsoft.com/en-us/office/vba/api/excel.name
[6]: https://support.microsoft.com/en-us/office/use-names-in-formulas-9cd0e25e-88b9-46e4-956c-cc395b74582a
[7]: https://learn.microsoft.com/en-us/office/vba/language/concepts/getting-started/visual-basic-naming-rules
[8]: https://github.com/rubberduck-vba/Rubberduck/issues/3074#issuecomment-309723538