Piping to Where-Object and Foreach.Object not working in module delayed loaded in $Profile
07:20 29 Jan 2024

Preamble: this is not about "fixing the code", as I already fixed it. This is about "understanding what went wrong, so to avoid similar mistakes in future"

Situation:
Powershell 7.4.1.

I use THIS piece of code(which I got from some website I cannot recall) in my $Profile to delay-load modules and scripts. The live one has more code but not relevant: I've tested this is where the trouble is.

Specifically, I use it to load a Module I wrote for personal use. (I know I don't actually need to load modules in my $Profile script as long as they are in my $env:PSModulePath; I'm sure there was a reason I did it but honestly cannot remember what.)

True contents of the original module do not matter as the Minimum Reproducible Example is:

scirpt in $profile

$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = [Text.Encoding]::UTF8
# https://seeminglyscience.github.io/powershell/2017/09/30/invocation-operators-states-and-scopes
$GlobalState = [psmoduleinfo]::new($false)
$GlobalState.SessionState = $ExecutionContext.SessionState

$Job = Start-ThreadJob -Name TestJob -ArgumentList $GlobalState -ScriptBlock {
    $GlobalState = $args[0]
    . $GlobalState {

        # We always need to wait so that Get-Command itself is available
        do {
            Start-Sleep -Milliseconds 200
        } until (Get-Command Import-Module -ErrorAction Ignore)

        
        # other dot-sourced scripts...
        # . "$ProfileDirectory\CustomAlias.ps1"
        # . "$ProfileDirectory\CustomConstants.ps1"
        # . "$ProfileDirectory\CustomVariables.ps1"
        # . "$ProfileDirectory\CustomPrompt.ps1"

        # Import-Module -Name Sirtao
        Import-Module -Name Get-DirectoryItem

    }
}



$null = Register-ObjectEvent -InputObject $Job -EventName StateChanged -SourceIdentifier Job.Monitor -Action {
    # JobState: NotStarted = 0, Running = 1, Completed = 2, etc.
    if ($Event.SourceEventArgs.JobStateInfo.State -eq 'Completed') {
        $Result = $Event.Sender | Receive-Job
        if ($Result) {
            $Result | Out-String | Write-Host
        }

        $Event.Sender | Remove-Job
        Unregister-Event Job.Monitor
        Get-Job Job.Monitor | Remove-Job
    }
    elseif ($Event.SourceEventArgs.JobStateInfo.State -gt 2) {
        $Event.Sender | Receive-Job | Out-String | Write-Host
    }
}

Module loaded

function Get-DirectoryItem {
    [CmdletBinding(DefaultParameterSetName = 'BaseSet')]
    [Alias('Get-Dir', 'GD')]
    param (
    )

    process {
        1..3 | Where-Object { $_ }
        1..3 | ForEach-Object { $_ }
        $a = @('a', 'b', 'c') 
        $a | ForEach-Object { $_ }
        $a | Where-Object { $_ }

    }
}

What I did try: simply running the command.

What I was expecting: the script returning the values of the arrays

What I got: the errors ForEach-Object: Object reference not set to an instance of an object. and Where-Object: Object reference not set to an instance of an object.

Please note that Get-Item, Get-ChildItem and Get-FileHash, the only other examples of piping I used in my modules, do work as expected

How i did fix it: removing the import from the Job. The module was still imported automagically and everything worked as expected. But as i said, this is not about fixing, but understanding.

So... any ideas?

powershell module profile runspace scriptblock