How to detect whether a script is dot-sourced, or loaded as part of a module using powershell v2+?

powershell, powershell-2.0, powershell-3.0

Solution

It appears that I found the answer to this. I was missing something in `$MyInvocation`, because I was looking in the wrong scope. Given the following files:

# .\moduleDetection\moduleDetection.ps1
$ErrorActionPreference = 'SilentlyContinue'; 
'=== Parent Invocation.MyCommand: [{0}]' -f (Get-Variable -Name:MyInvocation -Scope:1 -ValueOnly | Select -Expand MyCommand) | Out-Host;

.

# .\moduleDotSource\moduleDotSource.psm1
. "$PSScriptRoot\..\moduleDetection\moduleDetection.ps1"

.

# .\module-test.ps1
$Error.Clear()

Write-Host "Powershell Version 3:"
Write-Host "Powershell -Command Import-Module (direct)"
powershell -nologo -noprofile -Command { Import-Module .\moduledetection }

Write-Host "Powershell -Command Import-Module (dot-source)"
powershell -nologo -noprofile -Command { Import-Module .\moduleDotSource }

Write-Host "Powershell -File ...moduledetection.ps1"
powershell -nologo -noprofile -File .\moduledetection\moduleDetection.ps1

Write-Host "Powershell Dot-Source"
powershell -nologo -noprofile -Command { . .\moduledetection\moduleDetection.ps1 }

Write-Host ""
Write-Host "Powershell Version 2:"
Write-Host "Powershell -Version 2 -Command Import-Module"
powershell -version 2.0 -nologo -noprofile -Command { Import-Module .\moduledetection }

Write-Host "Powershell -Version 2 -Command Import-Module (dot-source)"
powershell -version 2.0 -nologo -noprofile -Command { Import-Module .\moduleDotSource }

Write-Host "Powershell -Version 2 -File ...moduledetection.ps1"
powershell -version 2.0 -nologo -noprofile -File .\moduledetection\moduleDetection.ps1

Write-Host "Powershell -Version 2 Dot-Source"
powershell -version 2.0 -nologo -noprofile -Command { . .\moduledetection\moduleDetection.ps1 }

and finally, make a symlink from the original ps1 to a psm1 with the correct name to load as a direct module.

cmd /c mklink .\moduledetection\moduleDetection.ps1 .\moduledetection\moduleDetection.psm1

The output shows that the parent scope has the key.

Output:

Powershell Version 3:
Powershell -Command Import-Module (direct)
=== Parent Invocation.MyCommand: [ Import-Module .\moduledetection ]
Powershell -Command Import-Module (dot-source)
=== Parent Invocation.MyCommand: [ Import-Module .\moduleDotSource ]
Powershell -File ...moduledetection.ps1
Powershell Dot-Source

Powershell Version 2:
Powershell -Version 2 -Command Import-Module
=== Parent Invocation.MyCommand: [ Import-Module .\moduledetection ]
Powershell -Version 2 -Command Import-Module (dot-source)
=== Parent Invocation.MyCommand: [ Import-Module .\moduleDotSource ]
Powershell -Version 2 -File ...moduledetection.ps1
Powershell -Version 2 Dot-Source

As we can see from the output shown (run on Server 2008 R2), the parent scope's `$MyInvocation.MyCommand` contains the import module statement. I have not tested it yet, but I infer from this that if it's multiple indirection through chained dot-sourcing, we can keep taking the parent scope until we get either Null or an Import-Module.

Now we know how to detect if we're in a module or not, and through other resources can also grok if we're being dot-sourced, executed directly, and/or loaded through a module.

Problem

Given a ps1 file as part of a module with the following code: ``` function Get-Greeting { 'Hello {0}' -f $Env:Username } Export-ModuleMember -Function:Get-Greeting ``` When loaded as part of the module, everything is fine. If I dot-source the script, I get ``` Export-ModuleMember : The Export-ModuleMember cmdlet can only be called from inside a module. ``` I know I could just add a -ErrorAction:Ignore on the Export-ModuleMember, but that's not the point. I'd like to have a script run differently whether it has been imported, or dot-sourced. In version 2, one could probably write a hack around the `$PSScriptRoot`, but that is just a hack, and doesn't work in version 3 where they "fixed" `$PSScriptRoot` to never be null. I've tried looking at various items in `$MyInvocation`, but either I've missed something, or it has nothing useful. I've also tried to run `Get-Variable` inside and outside a module, but again found no differences. What have I missed that is different when running as `Import-Module` vs `. myscript.ps1`?

Original source