Search code examples
powershellwindows-terminalpowershell-7

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


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.


Solution

  • 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.