4

Given code such as this MRE:

function Get-One {1}
Update-TypeData -TypeName 'Demo' -MemberType 'ScriptProperty' -MemberName 'Test1' -Value {Get-Date}
Update-TypeData -TypeName 'Demo' -MemberType 'ScriptProperty' -MemberName 'Test2' -Value {Get-One}
$example = [PSCustomObject]@{PSTypeName = 'Demo'}
$example

If I invoke it as pwsh -File '.\Demo.ps1' then all works as you'd expect / I get this output:

Test1               Test2
-----               -----
2021-04-17 21:35:55     1

However, if I invoke the same command as pwsh -Command '.\Demo.ps1' I get this (i.e. Test2 is blank); whilst I'd expect to get the same as the above:

Test1               Test2
-----               -----
2021-04-17 21:35:55     

i.e. When I invoke via the -Command parameter, the ScriptProperty can't access cmdlets/functions defined within the script itself; though can access standard ones (e.g. Get-Date).

I'd assume this was a bug; only the same behaviour's seem in both PWSH and PowerShell; which makes that a little less likely.

Is this a bug; or am I missing something in my understanding.

mklement0
  • 382,024
  • 64
  • 607
  • 775
JohnLBevan
  • 22,735
  • 13
  • 96
  • 178

1 Answers1

6

The difference in behavior is explained by the fact that -File implicitly dot-sources the target script file, which means that it runs in the global scope.

Typically, this detail is of little consequence (except if you also pass -NoExit to request staying in the session after the script terminates).

Here, it makes the crucial difference:

  • With -File, Get-One ends up defined in the global scope, which is the prerequisite for the ScriptProperty-defining { Get-One } script block being able to see it.

  • By contrast, running the script file with -Command does not dot-source it, making the Get-One command effectively invisible to the { Get-One } script block - and errors occurring inside such ScriptProperty-defining script blocks are quietly ignored.

There are two solutions:

  • Either: Explicitly define your Get-One function as global:

    function global:Get-One { 1 }
    
  • Or: When using -Command, explicitly dot-source the script:

     pwsh -Command '. .\Demo.ps1'
    
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • That's fantastic; thank-you. I've knocked up a cmdlet to test for the global context to run at the start of my script to ensure it's run correctly (possibly the GUID's overkill; just added in case the same global variable was assigned a value elsewhere). `function Test-GlobalContext { [Guid]$expected = [Guid]::NewGuid(); Set-Variable -Scope 1 -Name 'InGlobalContextTest' -Value $expected; [bool]($global:InGlobalContextTest -eq $expected); Remove-Variable -Scope 1 -Name 'InGlobalContextTest' ; }` – JohnLBevan Apr 18 '21 at 08:22
  • 1
    I'm glad to hear it, helped, @JohnLBevan. A simpler way may be `[bool] $isGlobalScope = -not $(try { Get-Variable -Scope 1 PSHOME } catch { })` - see [this answer](https://stackoverflow.com/a/59081086/45375). – mklement0 Apr 18 '21 at 15:03