1

I am trying to modify a variable within Invoke-Command in order to get out of a loop, however I'm having trouble doing that.

In the sample script below, I'm connecting to a host, grabbing information from NICs that are Up and saving the output to a file (Baseline). Then on my next iteration I will keep grabbing the same info and then compare Test file to Baseline file.

From a different shell, I've connected to the same server and disabled one of the NICs to force Compare-Object to find a difference.

Once a difference is found, I need to get out of the loop, however I cannot find a way to update the local variable $test_condition. I've tried multiple things, from Break, Return, $variable:global, $variable:script, but nothing worked so far.

$hostname = "server1"
$test_condition = $false

    do {
        Invoke-Command -ComputerName $hostname -Credential $credential -ScriptBlock{
            
            $path = Test-Path -LiteralPath C:\Temp\"network_list_$using:hostname-Baseline.txt"

            if ($path -eq $false) {
                Get-NetAdapter | Where-Object Status -EQ "Up"  | Out-File -FilePath (New-Item C:\Temp\"network_list_$using:hostname-Baseline.txt" -Force)

            } else {
                Get-NetAdapter | Where-Object Status -EQ "Up" | Out-File -FilePath C:\Temp\"network_list_$using:hostname-Test.txt"
                $objects = @{
                    ReferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Baseline.txt")
                    DifferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Test.txt")
                    }
                $test_condition = (Compare-Object @objects).SideIndicator -ccontains "<="
                $test_condition #this is returning True <-----

            }

        }
    } until ($test_condition -eq $true)

Any tips? What am I doing wrong?

TIA, ftex

3
  • 2
    Try adding this $test_condition = Invoke-Command -C........, your invocation is returning a boolean to stdout, you need to capture it in the variable to test it against the until (....) condition Commented May 11, 2021 at 22:05
  • Thanks @SantiagoSquarzon. It worked. I appreciate the help. Commented May 13, 2021 at 14:13
  • No problem :) Steven did all the hard work tho, thank him Commented May 13, 2021 at 14:42

1 Answer 1

3

You can pass variables into a remote script block with the $Using:VarName scope modifier, but you can't use typical $Global: or $Script to modify anything in the calling scope. In this scenario the calling scope isn't the parent scope. The code is technically running in a new session on the remote system and $Global: would refer to that session's global scope.

For example:

$var = "something"
Invoke-Command -ComputerName MyComuter -ScriptBlock { $Global:var = "else"; $var}

The remote session will output "else". However, after return in the calling session $var will output "something" remaining unchanged despite the assignment in the remote session.

Based on @SantiagoSquarzon's comment place the assignment inside the Do loop with a few other modifications:

$hostname = "server1"

do {
    $test_condition = $false
    $test_condition = 
    Invoke-Command -ComputerName $hostname -Credential $credential -ScriptBlock{
        
        $path = Test-Path -LiteralPath C:\Temp\"network_list_$using:hostname-Baseline.txt"

        if ($path -eq $false) {
            Get-NetAdapter | Where-Object Status -eq "Up"  | Out-File -FilePath (New-Item C:\Temp\"network_list_$using:hostname-Baseline.txt" -Force)

        } else {
            Get-NetAdapter | Where-Object Status -eq "Up" | Out-File -FilePath C:\Temp\"network_list_$using:hostname-Test.txt"
            $objects = @{
                ReferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Baseline.txt")
                DifferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Test.txt")
                }
            
            (Compare-Object @objects).SideIndicator -contains "<=" # this is returning True <-----
        }

    }
} until ($test_condition -eq $true)

I don't know why you were using -ccontains considering "<=" has no casing implications. Also it's very unusual to capitalize operators.

Notice there's no explicit return or assignment. PowerShell will emit the Boolean result of the comparison and that will be returned from the remote session and end up assigned to the $test_condition variable.

An aside:

I'm not sure why we want to use -contains at all. Admittedly it'll work fine in this case, however, it may lead you astray elsewhere. -contains is a collection containment operator and not really meant for testing the presence of one string within another. The literal meaning of "contains" makes for an implicitly attractive hazard, as demonstrated in this recent question.

In short it's easy to confuse the meaning, purpose and behavior on -contains.

This "<=" -contains "<=" will return "true" as expected, however "<==" -contains "<=" will return "false" even though the left string literally does contain the right string.

The answer, to the aforementioned question says much the same. My addendum answer offers a some additional insight for the particular problem and how different operators can be circumstantially applied.

So, as a matter of practice for this case wrap the Compare-Object command in the array sub-expression operator like:

@( (Compare-Object @objects).SideIndicator ) -contains "<="

Given the particulars, this strikes me as the least intrusive way to implement such a loosely stated best practice.

Sign up to request clarification or add additional context in comments.

11 Comments

Yep, great answer as always. I'm so not used to Compare-Object honestly, never find a good use case for it vs a more classic approach, maybe i'm too old lol. I would recommend OP using a PSSession for this use case too and -Session instead of -ComputerName on icm.
Thanks! Yes now that you mention it, as written amd considering it's a loop we should indeed use a PSSession. Moreover, we should probably introduce a Start-Sleep command. However, It doesn't look like there's a good reason to outside Invoke-Command, instead we could probably move it in to the script block. That would diminish the case for PSSessions, not having to setup/teardown repeatedly. I think I'll wait for comment before amending.
Nicely done; note that you never need @(...) in order to use -contains - even a scalar LHS will do; e.g.; 1 -contains 1 works just fine.
@mklement0 Thanks. I edited the answer, hopefully to better convey the intended message. I included a recent question that shows what I think is a common point of confusion with the -contains operator. In any event, your feedback is always welcomed; do let me know what you think.
Thanks for updating, Steven (you already had my +1). I assume you're saying that use of @(...) with -contains isn't a technical necessity but signals to the reader that the LHS of -contains is a collection operator. However, even -match and -like can situationally act like collection operators, namely if the LHS is a collection, whereas -contains always acts always treats its LHS as a collection. To me, the @(...) therefore suggests the opposite of what you're trying to convey, namely that situationally @(...) is necessary - which it isn't.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.