Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 323
Eris Avatar asked Jul 25 '13 20:07

Eris


2 Answers

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.

like image 153
Eris Avatar answered Oct 13 '22 10:10

Eris


This doesn't directly answer your question but may help you achieve your goal.

Use the -Function or -Cmdlet parameters of import-module to selectively import parts of the module.

http://technet.microsoft.com/en-us/library/hh849725.aspx

-Function<String[]>

Imports only the specified functions from the module into the current session. Enter a list of functions. Wildcard characters are permitted. Some modules automatically export selected functions into your session when you import the module. This parameter lets you select from among the exported functions.

-Cmdlet<String[]>

Imports only the specified cmdlets from the module into the current session. Enter a list of cmdlets. Wildcard characters are permitted. Some modules automatically export selected cmdlets into your session when you import the module. This parameter lets you select from among the exported cmdlets.

like image 44
Andy Arismendi Avatar answered Oct 13 '22 08:10

Andy Arismendi