Can a powershell module call functions in its importer's scope?

If I understood correctly what you're trying to do, there are 2 commonly used ways to load custom functions to your main script. I'll give you a few examples, since, I'm not sure if there is a best practice for this.

The script being executed will always be main.ps1 on all given examples.

  • Example 1: All functions are stored on one file

Folder structure

../path/to/script/main.ps1
../path/to/script/Functions/functions.ps1

Functions.ps1

function myCustomFunction1 {
....
}

function myCustomFunction2 {
....
}

function myCustomFunction3 {
....
}

main.ps1

On the first lines of code you could add something like this:

$ErrorActionPreference = 'Stop'

# Option 1: Using Import-Module.
try
{
    Import-Module "$PSScriptRoot\Functions\functions.ps1"
}
catch
{
    "Failed to load dependency Functions.`nError: $_"
}

# Option 2: Dot Sourcing
try
{
    . "$PSScriptRoot\Functions\functions.ps1"
}
catch
{
    "Failed to load dependency Functions.`nError: $_"
}

Note: both options will load ALL functions.

  • Example 2: Many functions stored on different files. This is used when you have lots of complex and/or lengthy functions.

Folder structure

../path/to/script/main.ps1
../path/to/script/Functions/myCustomFunction1.ps1
../path/to/script/Functions/myCustomFunction2.ps1
../path/to/script/Functions/myCustomFunction3.ps1

myCustomFunction1.ps1

function myCustomFunction1 {
....
}

myCustomFunction2.ps1

function myCustomFunction2 {
....
}

myCustomFunction3.ps1

function myCustomFunction3 {
....
}

main.ps1

On the first lines of code you could add something like this:

$ErrorActionPreference = 'Stop'

# Option 1: Using Import-Module.
try
{
    Get-ChildItem "$PSScriptRoot\Functions\*.ps1" | Import-Module
}
catch
{
    "Failed to load dependency Functions.`nError: $_"
}

# Option 2: Dot Sourcing
try
{
    Get-ChildItem "$PSScriptRoot\Functions\*.ps1" | ForEach-Object {
        . $_.FullName
    }
}
catch
{
    "Failed to load dependency Functions.`nError: $_"
}

From inside an (advanced) function in your module, you can use $PSCmdlet.InvokeCommand.InvokeScript() with argument $PSCmdlet.SessionState and a script block created from a string to execute arbitrary code in the caller's scope.

The technique was gratefully adapted from this comment on GitHub.

For brevity, the following code demonstrates the technique with a dynamic module created with New-Module, but it equally applies to regular, persisted modules:

# The function to call from the module.
function Execute-Payload {
  "Inside Execute-Payload in the script's scope."
}

# Create (and implicitly import) a dynamic module.
$null = New-Module {

  # Define the advanced module function that calls `Execute-Payload`
  # in its caller's scope.
  function Invoke-Something {
    [CmdletBinding()]
    param()

    $PSCmdlet.InvokeCommand.InvokeScript(
      $PSCmdlet.SessionState,
      # Pass a *string* with arbitrary code to execute in the caller's scope to 
      # [scriptblock]::Create().
      # !! It is imperative that [scriptblock]::Create() be used, with
      # !! a *string*, as it creates an *unbound* script block that then 
      # !! runs in the specified session. 
      # !! If needed, use string expansion to "bake" module-local variable 
      # !! values into the string, or pass parameters as additional arguments.
      # !! However, if a script block is passed *by the caller*,
      # !! bound to *its* context, it *can* be used as-is.
      [scriptblock]::Create(' Execute-Payload ')
    )

  }

}

# Now invoke the function defined in the module.
Invoke-Something

The above outputs Inside Execute-Payload in the script's scope., proving that the script's function was called.