Why does wrapping a script block into a commandlet function swallow all output?

59 views Asked by At

Below script works perfectly fine when it is directly executed from a .ps1 file, but ever since i put it in a .psm1 file the function still works, but does no longer redirect the output from &dotnet ... to the output. I am looking for a way to make it print its output from within the function too. Moving it back to the ps1 is not an option.

I have tried a variation of

Attempts

& dotnet *>&1
& dotnet | Out-Host
& dotnet | Write-Host

but apparently my powershell knowledge is too limited here.

functions.psm1:


function Build-Android {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ValueFromPipeline)]
        [string] $KeyStorePath, 

        [Parameter(Mandatory,ValueFromPipeline)]
        [string] $SignPassword, 

        [Parameter(Mandatory,ValueFromPipeline)]
        [string] $ProjectPath, 

        [Parameter(Mandatory,ValueFromPipeline)]
        [string] $PublishDirectory
    )

    [Environment]::SetEnvironmentVariable('PCR3PW', $SignPassword, 'Process')

    $keyAlias = "PCR3"
    $keyPass = "env:PCR3PW"
    $storePass = "env:PCR3PW"

    # https://learn.microsoft.com/de-de/dotnet/maui/android/deployment/publish-cli 
    $publishCode = "dotnet publish $ProjectPath -f net7.0-android -c Release -o `"$PublishDirectory`" -p:AndroidKeyStore=true -p:AndroidSigningKeyStore=$KeyStorePath -p:AndroidSigningKeyAlias=$keyAlias -p:AndroidSigningKeyPass=$keyPass -p:AndroidSigningStorePass=$storePass"
    Write-Host "$publishCode" -ForegroundColor DarkGreen

    #Invoke-Expression -Command $publishCode -ErrorAction Stop
    &dotnet publish $ProjectPath -f net7.0-android -c Release -o `"$PublishDirectory`" -p:AndroidKeyStore=true -p:AndroidSigningKeyStore=$KeyStorePath -p:AndroidSigningKeyAlias=$keyAlias -p:AndroidSigningKeyPass=$keyPass -p:AndroidSigningStorePass=$storePass
    
    if($LASTEXITCODE -ne 0)    
        throw "There was an issue running the specified dotnet command."

    $apk = Get-ChildItem "$PublishDst" -Filter "*.apk" | %{$_.FullName}

    return $apk
}

MyScript.ps1:

param (
    [Parameter(Mandatory=$true, HelpMessage="Signing password for publish process")]
    [string]$SignPassword,
    [Parameter(Mandatory=$true, HelpMessage="Destination folder for APK publish")]
    [string]$PublishDst
)

Import-Module ".\functions.psm1"

$keyStore = [System.Uri]::new([System.Uri]::new("$PSScriptRoot\.", [System.UriKind]::Absolute), [System.Uri]::new("PCR3.keystore", [System.UriKind]::Relative)).LocalPath
$apkProject = [System.Uri]::new([System.Uri]::new("$PSScriptRoot\.", [System.UriKind]::Absolute), [System.Uri]::new("..\src\MyProject\MyProject.csproj", [System.UriKind]::Relative)).LocalPath

$apkPath = Build-Android -KeyStorePath $keyStore -SignPassword $SignPassword -ProjectPath $apkProject -PublishDirectory $PublishDst

References checked

I have looked into this to look for clues on how to redirect my output: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-7.3

Underlying issue

The cause of this apparently in some way is Terminal. While $PSVersionTable is the same as PS Core 7 - Terminal produces no output from the & expression while powershell itself does.

1

There are 1 answers

10
Mathias R. Jessen On BEST ANSWER

Look at the call site:

$apkPath = Build-Android -KeyStorePath $keyStore -SignPassword $SignPassword -ProjectPath $apkProject -PublishDirectory $PublishDst

You're assigning the output from Build-Android to the local variable $apkPath, so that's where the output will end up.

If you want to sidestep the normal flow of output and instead print it to the host application immediately, use Out-Host:

&dotnet publish $ProjectPath -f net7.0-android -c Release -o `"$PublishDirectory`" -p:AndroidKeyStore=true -p:AndroidSigningKeyStore=$KeyStorePath -p:AndroidSigningKeyAlias=$keyAlias -p:AndroidSigningKeyPass=$keyPass -p:AndroidSigningStorePass=$storePass |Out-Host

Here's a simplified example showing the effect with ping.exe instead:

PS ~> @'
>> function f {
>>   [CmdletBinding()]
>>   param()
>>
>>   ping.exe 1.1.1.1 -n 1 |Out-Host
>>
>>   return 'Not from ping'
>> }
>> '@ |Set-Content myModule.psm1
PS ~> Import-Module myModule.psm1 -Force
PS ~> $functionOutput = f

Pinging 1.1.1.1 with 32 bytes of data:
Reply from 1.1.1.1: bytes=32 time=13ms TTL=56

Ping statistics for 1.1.1.1:
    Packets: Sent = 1, Received = 1, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 13ms, Maximum = 13ms, Average = 13ms
PS ~> $functionOutput
Not from ping

As you can see, the output piped to Out-Host is written directly to the host application's screen buffer immediately as f executes, whereas the regular output that isn't ends up in the variable.