Powershell invoke-command passing an object

4.8k views Asked by At

I have created a list of objects, $LIST. Each object in the list has several attributes, including FQDN and Services. FQDN is the fully qualified server name and the services are the list of services I want to check on the remote server.

I'll start with:

$LIST = <CALL to Module function to populate the server information>

Next is the call to the invoke-command

Invoke-Command -ComputerName $LIST.FQDN -ScriptBlock { 
Write-Host "Working on $($env:ComputerName)" 
    Get-Service
}

But what I need to do is pass the list of services that correspond to -ComputerName. I know I can use the -ArgumentList and I've tried:

Invoke-Command -ComputerName $LIST.FQDN -ScriptBlock { 
    Param ([string[]] $ServiceList)
    Write-Host "Working on $($env:ComputerName)"
    ($ServiceList -split(",")).trim() | %{ 
        $svc =Get-Service $_ 
        $Svc
    }
} -ArgumentList $LIST.Services

But this passes a list of all the services for every server. I can do this:

$LIST | %{
    $Server = $_

    Invoke-Command -ComputerName $Server.FQDN -ScriptBlock { 
        Param ([string[]] $ServiceList)
        Write-Host "Working on $($env:ComputerName)"
        ($ServiceList -split(",")).trim() | %{ 
            $svc =Get-Service $_ 
            $Svc
        }
    } -ArgumentList $($Server.SERVICES)
}

But then I loose the advantage of parallelism of the invoke-command CmdLet.

How do I pass the list of services for the specific ComputerName being processed?

3

There are 3 answers

0
Duncan On

I don't think there is an easy way to pass your list through Invoke-Command, so perhaps you need to think of alternative approaches that will let each target computer run an identical command.

If the list of services is specific to each remote computer, and is the same every time you run the commands then you could simply store the parameter on each target computer in the registry or a file. Ideally you set that up using a configuration manager such as DSC, Puppet, or Chef.

You could dump the parameters out to files on the local computer and let each target computer connect back to a network share and fetch the file corresponding to its name. If a network share connection isn't possible then a simple web service or a database connection would be other ways you could let each computer fetch the service list.

Are there groups of targets that will receive the same list? Say you have 10 different service lists each to be sent to a group of 100 computers. In that case maybe you could choose an arbitrary computer from each group and send those computers an identical command with all of the data. Each computer you target then figures out which group it is in and distributes the identical command across all of its siblings. This would also have the benefit of increasing the parallelism.

Or just create one set of services that is the union of all the service lists, send that to every machine, and filter the results based on which you wanted. That could be the simplest solution:

PS C:\WINDOWS\system32> $allServices = 'WerSvc','WinRM','ZeroConfigService','AnotherService'

PS C:\WINDOWS\system32> Invoke-Command -ComputerName . -ScriptBlock {
  Get-Service -Name $args -ErrorAction Ignore
 } -ArgumentList $allServices

Status   Name               DisplayName                            PSComputerName                                                                                                      
------   ----               -----------                            --------------                                                                                                      
Stopped  WerSvc             Windows Error Reporting Service        localhost                                                                                                           
Running  WinRM              Windows Remote Management (WS-Manag... localhost                                                                                                           
Running  ZeroConfigService  Intel(R) PROSet/Wireless Zero Confi... localhost     

will simply ignore any services you asked for that aren't installed on that particular machine.

0
Micky Balladelli On

For the sake of testing, I've created two objects containing a pointer to localhost, and a list of specific services to verify. Goal is to invoke background jobs that will run remotely and in parallel, and verify the provided list of services.

One of the key parts in the script below is the generated array for the Invoke-Command argumentlist, which contains the services array for the associated computer. The rest is pretty much your code. Finally I use Get-Job | Receive-Job to retrieve the output from the Invoke-Command jobs.

$LIST = @()

$Computer1 = New-Object PSObject -Property @{
        FQDN = "localhost";
        SERVICES = @()
    }
$Computer1.SERVICES += "W32Time"
$Computer1.SERVICES += "vmms"
$LIST += $Computer1

$Computer2 = New-Object PSObject -Property @{
        FQDN = "localhost";
        SERVICES = @()
    }
$Computer2.SERVICES += "ShellHWDetection"
$Computer2.SERVICES += "SharedAccess"
$LIST += $Computer2

$LIST | %{
    $Server = $_
    $arguments = @(,($Server.SERVICES))
    Invoke-Command -ComputerName $Server.FQDN -AsJob -ScriptBlock { 
        Param ([string[]]$ServiceList)
        Write-Host "Working on $($env:ComputerName)"
                ($ServiceList -split(",")).trim() | %{ 
            $svc =Get-Service $_ 
            $Svc
        }
    } -ArgumentList $arguments
}
Get-Job | Receive-Job

Output is this:

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command                  
--     ----            -------------   -----         -----------     --------             -------                  
28     Job28           RemoteJob       Running       True            localhost             ...                     
30     Job30           RemoteJob       Running       True            localhost             ...                     
Working on CORSAIR-PC

Status   Name               DisplayName                            PSComputerName                                                                                                                         
------   ----               -----------                            --------------                                                                                                                         
Stopped  W32Time            Windows Time                           localhost                                                                                                                              
Running  vmms               Hyper-V Virtual Machine Management     localhost                                                                                                                              
Working on CORSAIR-PC
Running  ShellHWDetection   Shell Hardware Detection               localhost                                                                                                                              
Stopped  SharedAccess       Internet Connection Sharing (ICS)      localhost  

So, distinct services per computer were passed to Invoke-Command which runs in parallel jobs using the -AsJob parameter.

0
walid toumi On

If $LIST["COMPUTER"].SERVICES return list service of COMPUTER you can test in your scriptblock for every object passed..maybe This but not tested: (in pseudo-code)

Icm -comp $LIST.FQDN -Script {
   Param($obj)
        $c=$env:computername
        $obj["$c"].services
} -arg $LIST