Returning a variable from Invoke-Command

This topic contains 5 replies, has 3 voices, and was last updated by  PowershellNoob 6 months, 1 week ago.

  • Author
    Posts
  • #91253

    PowershellNoob
    Participant

    Hello there everyone,
    So I have been trying to figure out without much luck how to return a variable in a remote session. Essentially I am trying to store the hostname of the remote client into a variable array if the conditions are either true or false and return them to the local session.
    Ex.

    $Computers = (Get-Adcomputer -filter *).Name
    [array]$TrueComputers = $Null
    [array]$FalseComputers = $Null
    
    foreach($Computer in $Computers)
    {
        invoke-command -ComputerName $Computer -ScriptBlock {
            if ($True)
            {
                write-output "$Using:Computer meets requirements"
                $TrueComputer += $Using:Computer
            }
    
            else 
            {
                write-output "$Using:Computer does not meet requirements"
                $FalseComputer += $Using:Computer
            }
        }
    }
    

    I need to be able to return either the $TrueComputer or the $FalseComputer back to the local session to be able to output these to a file.
    Thank you for the help in advance and I apologize if this is a dumb question.

  • #91255

    Don Jones
    Keymaster

    So... I may not be following, but I don't understand where $Using is coming into play. $Using transmits a variable from the local session into the remote session. You can't use $Using to "reach back" and modify a variable in the local session; $Using is one-way.

    The correct way to return data would be to Write-Output. You're using Write-Output to spit out a human-readable message, though, which doesn't do PowerShell any good.

    $props = @{ComputuerName=$Computer
               Status=$False
               Message="Does not meet requirements"}
    New-Object -Type PSObject -Prop $props
    

    That will send back an object with three properties to the local session, which could then append it to a collection or whatever.

    Further, I'd eliminate the ForEach loop, and just jam $Computers into the -ComputerName parameter of Invoke-Command. Let it handle the parallelism – which it does very well indeed. Also,s top screwing with arrays. This isn't VBScript :).

    $results = invoke-command -ComputerName $Computers -ScriptBlock {
            $props = @{ComputerName=$env:COMPUTERNAME}
            if ($True)
            {
                $props.Add('Status',$True)
                $props.Add('Message','Yay!')
            }
    
            else 
            {
                $props.Add('Status',$False)
                $props.Add('Message','Boo!')
                
            }
            New-Object -Type PSObject -Prop $Props
        }
    }
    

    New-Object already emits the new object to the pipeline, so I don't need to use Write-Object.

  • #91259

    PowershellNoob
    Participant

    Thank you Don,
    Again you prove to be a gentleman and a scholar. My apologies I am still pretty new to Powershell and I am the only one here that has spent the time in this company to try to learn it. (Which I used your course on CBT Nuggets)Also first thing to any sort of programming that I've ever done.

    So we are trying to implement Chocolatey in our environment for software management and we are trying to see which computers actually have Chocolatey installed. I am just checking for the existence of the chocolatey folder in the ProgramData directory.
    Any Computers that return a false, we want to output the hostnames to a file, along with the ones that are currently not on the network.

    The point for utilizing $Using rather than specifying $Env:ComputerName was just to show that I knew how to pass the local variable into the invoked session, I just did not know how to return them.
    I tried it your way and it was much faster, but what would be the best way to keep track of failed invoke-commands if the computers happen to not have any network connectivity or happen to just not be on the network?

    Here is my thought process(along with your outstanding suggestions), (Again I apologize for beginner code)

    Function check-forChocolatey
    {
        [Cmdletbinding()]
        param 
        (
            [string[]]$ComputerName
        )
    
        $ChocofilePath = "C:\ProgramData\chocolatey"
    
        if (!($ComputerName))
        {
            $Computers = (Get-ADComputer -LDAPFilter "(Name=*WS1*)").Name
        }
        
            $Result = Invoke-Command -ComputerName $Computers -ScriptBlock {
                $Props = @{ComputerName = $env:COMPUTERNAME}
    
                if(Test-Path -Path "C:\ProgramData\chocolatey")
                {
                    $Props.Add('Installed',$true)
                }
    
                else
                {
                    $Props.Add('Installed',$false)
                }
    
                New-Object -TypeName PSObject -Property $Props
            } -ErrorAction SilentlyContinue
    
        $Result 
    
    }
    
  • #91265

    Don Jones
    Keymaster

    There are a bunch of ways you could test for failures. To keep Invoke-Command's parallelism, it's a bit harder. I'd probably get the results back, use Select and -Expand to extract the computer name property I got back, and then Diff that with my original computer name array. You an then Where the results so you get a list of computers from whom you got no results.

    Diff $Computers $Result.ComputerName | where sideindicator -eq '<=' Or whatever the side indicator property spews out. Something like that. Then you'd know who you were missing.

  • #91289

    Sam Boutros
    Participant

    I would write this function a little differently:

    #Requires -Modules ActiveDirectory
    
    Function check-forChocolatey {
        [Cmdletbinding()]
        param (
            [Parameter(Mandatory=$false)][String[]]$ComputerName = (Get-ADComputer -LDAPFilter "(Name=*WS1*)").Name,
            [Parameter(Mandatory=$false)][String]$ChocofilePath = "$env:ProgramData\chocolatey"
        )
    
        Begin {}
    
        Process {    
            Invoke-Command -ComputerName $ComputerName -ErrorAction SilentlyContinue -ScriptBlock {
    
                $Props = @{ComputerName = $env:COMPUTERNAME}
    
                if (Test-Path -Path $Using:ChocofilePath) {
                    $Props.Add('Installed',$true)
                } else {
                    $Props.Add('Installed',$false)
                }
    
                New-Object -TypeName PSObject -Property $Props
    
            } | select ComputerName,Installed # removing the PSComputerName and RunspaceId properties
        }
    
        End {}
    }
    

    – I would spell out the module dependency
    – I would paramterize Chocofilepath and actually use it in the function
    – I would remove the unneeded PSComputerName and RunspaceId properties

    Possible enhancements that I typically include in such a function include:
    – Removing ActiveDirectory module dependency by using an LDAP query
    – Accounting for the scenarios:
    1. Computers removed from the environment but AD object left behind
    2. Offline computers (no response to ping or on standard TCP ports 135,3389,5985,5986)
    3. Linux computers that are part of the domain/realm (response to ping and/or TCP 22,111)
    4. Windows computers not running PowerShell, or PS remoting not configured or/and enabled
    5. Windows computers to which the user running the script has no permission for PS remoting

  • #91307

    PowershellNoob
    Participant

    Gentlemen,
    Thank you both for the amazing tips. All of this creates makes the learning experience great. I was able to accomplish the task thanks to your wonderful advice and input. Don, your teachings has done great things for a lot of people that you do not know and me being one of the many I thank you.
    Sam thank you for these amazing tips as well. Your gave great tips and they are tips that I will be using from now on in everything that I code in Powershell. Thank you both for taking the time to do this and provide such wonderful feedback.
    May the both of you have an amazing weekend.

You must be logged in to reply to this topic.