powershellusinginvoke-command

Powershell GUI - Output to textbox from Invoke-Command function


I'm converting my current backup solution for our whole environment to Powershell GUI application. My script is working as intended but now I'm trying to display the output to my GUI textbox field.

The output I'm trying to display is inside a function that's calling an Invoke-Command scriptblock. Here's a snipped of my code:

function ZipFiles {
    Invoke-Command -ComputerName $servers -ScriptBlock {
        # Store the server name in a variable
        $hostname = hostname
        # Specify the 7zip executable path
        $7zipPath = "$env:ProgramFiles\7-Zip\7z.exe"
        # Throw an error if 7zip is not installed on the server        
        if (-not (Test-Path -Path $7zipPath -PathType Leaf)) {
            throw "7 zip file '$7zipPath' not found on $hostname"
            $7zipInstalled = $false
        }
        # Confirm that 7zip is installed on the remote server
        else {
            $7zipInstalled = $true
            Write-Host -ForegroundColor Cyan "7zip found on $hostname. Zipping files..."

I'm importing my layout from a XAML file and my TextBox item is stored under a variable named $var_textInfo. Since this variable is outside my Invoke-Command block, I need to call it with the using:$var_textInfo syntax.

I tried replacing the Write-Host line to instead output the result to the textbox using this command:

$using:var_textInfo.AppendText("7zip found on $hostname. Zipping files...")

However, Powershell doesn't seems to let me call methods with the using expression. It throws the following error message:

Expression is not allowed in a Using expression.

Any idea how I could properly output the text from my Invoke-Command function to my textbox ?

Thanks in advance for your ideas !


Solution

  • I need to call it with the using:$var_textInfo syntax

    No, you don't. The Invoke-Command script block runs on the remote machine(s) and doesn't have access to your local GUI objects. In general, you can't pass live objects to remote scripts, only data that can be serialized.

    Instead, pipe Invoke-Command to ForEach-Object and update your GUI (locally) from there:

    Invoke-Command -ComputerName $servers -ScriptBlock {
        # Store the server name in a variable
        $hostname = hostname
        # Specify the 7zip executable path
        $7zipPath = "$env:ProgramFiles\7-Zip\7z.exe"
        # Throw an error if 7zip is not installed on the server        
        if (-not (Test-Path -Path $7zipPath -PathType Leaf)) {
            throw "7 zip file '$7zipPath' not found on $hostname"
            $7zipInstalled = $false
        }
        # Confirm that 7zip is installed on the remote server
        else {
            $7zipInstalled = $true
            # Implicit output that can be captured by next pipeline command.
            "7zip found on $hostname. Zipping files..."
        }
    } | ForEach-Object {
        # Append text line received from remote script - this code runs locally!
        $var_textInfo.AppendText( $_ )
    }
    

    To be able to process output of the remote script, you have to write it to the success stream, either using Write-Output (rarely used) or implicitly, by just writing a string literal on its own line (as I did above). Any command that already outputs to the success stream will also be received by the local ForEach-Object.