1

I have a script which loads a function from an external source, I want to keep the function (ConvertDecToHex) loaded after executing the load function (LoadFunction).

Below is an example of extremely simplified code:

function LoadFunction ()
{
    $Command = 'function ConvertDecToHex ([int]$value){return "{0:x}" -f $value}'
    Invoke-Expression -Command ($Command)

    ConvertDecToHex 12 #works here
}

LoadFunction
ConvertDecToHex 12 #doesn't work here
Xenixium
  • 35
  • 3

2 Answers2

1

Apply Dot sourcing operator .

Runs a script in the current scope so that any functions, aliases, and variables that the script creates are added to the current scope, overriding existing ones. Parameters declared by the script become variables.

Fixed script:

function LoadFunction ()
{
    $Command = 'function ConvertDecToHex ([int]$value){return "{0:x}" -f $value}'
    Invoke-Expression -Command ($Command)

    ConvertDecToHex 12 #works here
}

. LoadFunction     # dot source
ConvertDecToHex 12

Output: D:\PShell\SO\68016859.ps1

c
c

Dot source the script as well if you want to use the ConvertDecToHex outside the script:

PS D:\PShell> . D:\PShell\SO\68016859.ps1
c
c
PS D:\PShell> ConvertDecToHex 12
c
PS D:\PShell>
JosefZ
  • 28,460
  • 5
  • 44
  • 83
  • It's not a bad idea but I want to do it in the function if possible. The point source has a downside that everything is in current scope and I don't want it – Xenixium Jun 17 '21 at 11:35
1

JosefZ's helpful answer is probably the best approach.

While it is true that ., the dot-sourcing operator runs everything directly in the caller's scope, including thereby making potentially auxiliary variables appear, there is is no need to use aux. variables in your case (and you can generally hide definitions you don't want the caller to see by running inside a child scope, via & { ... })):

function LoadFunction ()
{
  # Just define the function directly.
  # When the enclosing LoadFunction function is dot-sourced
  # ConvertDecToHex is defined in the caller's scope.
  function ConvertDecToHex ([int]$value){return "{0:x}" -f $value}
}

# Important: Dot-source (.) the LoadFunction call
. LoadFunction

# Now this should work.
ConvertDecToHex 12

Note:

  • The to-be-exported function is simply defined directly and normally inside the LoadFunction body - no need for Invoke-Expression (iex), which should generally be avoided

  • To comply with PowerShell's naming conventions, it's better to name your functions Import-Function and ConvertTo-Hex.


If you want to avoid dot-sourcing, you can use Import-Module:

If you place the functions to import in a separate *.ps1 file, you can use Import-Module to achieve the same effect - but note that in effect that is nothing more than a - more descriptive - syntax alternative to dot-sourcing such a file - unless you use a stand-alone .psm1 module file; see below.

E.g, place the function definition(s) in a file named functions.ps1:

function ConvertDecToHex([int]$value) { return "{0:x}" -f $value }

Then call Import-Module to import them, i.e. to make the function(s) available in the caller's scope:

# Note: The "./" (or ".\") prefix is required in order to import
#       from the current dir.
Import-Module ./functions.ps1

Note:

  • Import-Module, as the name suggests, is designed to import modules, but it can also be used with regular script files (.ps1).

  • Modules, even though they're typically and more robustly implemented as directories with a prescribed structure of related files, can also be implemented as stand-alone files, namely with filename extension .psm1 (note the m).

    • Therefore, you could alternatively save your function in file functions.psm1 rather than functions.ps1, for instance, but note that resulting behavior differs:

    • Modules create their own scope domain (a.k.a session (sub)state) that parallels the scopes in which non-module code runs; the only ancestral scope they share is the global scope, whereas the definitions from a non-global non-module scope - such as inside a regular .ps1 script - is not seen by functions imported from modules.

      • This behavior can have unexpected side effects with respect to preference variables - see GitHub issue #4568.
    • The upshot is:

      • If you use Import-Module ./functions.ps1 (regular script file), the imported functions will see the caller's definitions (functions, variables, ...). For instance, thanks to PowerShell's dynamic scoping, a variable $foo defined in the caller's scope would be visible in the body of your ConvertDecToHex function.

      • If you use Import-Module ./functions.psm1 (stand-alone module file), they will not.
        Conversely, the caller will not see variables defined in the .psm1 file, unless they're explicit exported with Export-ModuleMember.
        You may prefer this behavior, because it hides auxiliary variables by default, and because Export-ModuleMember also gives you control over what functions and aliases should be exported, so you can also hide auxiliary functions.

mklement0
  • 382,024
  • 64
  • 607
  • 775