"Parallel" queries in functions

Tagged: ,

This topic contains 3 replies, has 3 voices, and was last updated by Profile photo of strange-walker strange-walker 8 months, 3 weeks ago.

Viewing 4 posts - 1 through 4 (of 4 total)
  • Author
    Posts
  • #33469
    Profile photo of strange-walker
    strange-walker
    Participant

    Hi folks! I'm trying to master functions in powershell and I have a problem with my functions running queries "one-by-one".
    So I have this sample function which I want to collect data from several computers in Domain (see below).
    I have noticed that this command

    `'pc01', 'pc02', 'pc03' | get-diskspace `

    queries pc01, gets result and only after that sends next query to pc02. As far as I know powershell is able to split this query into 3 and send it to all 3 computers concurrently.

    So my questin is: what have I done wrong? Why it isn't working as planned?

    `function Get-DiskSpace
    {
        [CmdletBinding()]
        Param
        (
            # NetBios name of computer
            [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
            [string]$ComputerName = 'localhost'
    
        )
    
        Begin
        {
        $report = @()
        }
        Process
        {
        $disk = Get-WmiObject -Class Win32_LogicalDisk -Filter "DriveType=3" -ComputerName $ComputerName | 
        Select DeviceID, 
            @{n='Size'; e={$_.size / 1gb -as [int]}},
            @{n='Free'; e={$_.freespace / 1gb -as [int]}}
    
    
    
        foreach ($item in $disk.deviceid) {
            $i=[array]::IndexOf($disk.deviceid, $item)
            $letter = "Disk $item"
            $value = $disk.size[$i]
            $disk2 = New-Object PSObject
            $disk2 | Add-Member -type NoteProperty -Name 'Name' -Value $item
            $disk2 | Add-Member -type NoteProperty -Name 'Capacity' -Value $disk.size[$i]
            $disk2 | Add-Member -type NoteProperty -Name 'Freespace' -Value $disk.Free[$i]
            $report += $disk2
            }
    
        }
        End
        {
        $report
        }
    }`
    

    P.S. Sorry for my bad English

    #33470
    Profile photo of Tim Pringle
    Tim Pringle
    Participant

    Hi StrangeWalker,

    It won't work concurrently. You need to specify some form of asynchrounous operation. For remote operations, i'd recommend instead performing the actions as jobs instead of using pipeline operations.

    I'd typically do this within an Invoke-Command -Computername @('comp1','comp2') -Scriptblock {} -AsJob operation. At the end of your code perform a Get-Job | Wait-Job to pause until all remote jobs have completed. You can then use Receive-Job to gather the output.

    There are, however, other ways to do this.

    #33474
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    In this particular case, it's easier to just make one call to Get-WmiObject in your function's End block, passing in all of the computer names. This is because Get-WmiObject already works concurrently when you give it multiple computer names, and you don't have to do any of the work of managing that concurrency yourself.

    When you do need to do things yourself, the code can get quite complicated, but lots of people have been working on making this easier, and one such project is PoshRSJob from Boe Prox (see http://learn-powershell.net/2015/03/31/introducing-poshrsjob-as-an-alternative-to-powershell-jobs/ ).

    Anyhow, here's how I'd tweak your code to make it faster with just Get-WmiObject:

    function Get-DiskSpace
    {
        [CmdletBinding()]
        Param
        (
            # NetBios name of computer
            [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
            [string]$ComputerName = 'localhost'
    
        )
    
        Begin
        {
            $computers = New-Object System.Collections.ArrayList
        }
        Process
        {
            $null = $computers.Add($ComputerName)
        }
        End
        {
            $selectProperties = @(
                'PSComputerName'
                @{ Name = 'Name'; Expression = { "Disk $($_.DeviceID)" } }
                @{ Name = 'Size'; Expression = { $_.size / 1gb -as [int] } }
                @{ Name = 'Free'; Expression = { $_.freespace / 1gb -as [int] } }
            )
    
            Get-WmiObject -Class Win32_LogicalDisk -Filter "DriveType=3" -ComputerName $computers | 
            Select-Object -Property $selectProperties
        }
    }
    
    #33531
    Profile photo of strange-walker
    strange-walker
    Participant

    Thanks for your answers!

    I dont trust Invoke-Command because of two reasons:

    First, it uses local instance of powershell and its version can vary anywhere between v2 and v5
    Second, it makes impossible to use my favorite custom-build functions. And keeping track of cmdlets used in code may become a challenge

    So, I guess I will have to dig into PSJobs. At least now I know that my goal cannot be achieved the way I have tried. Thanks again!

Viewing 4 posts - 1 through 4 (of 4 total)

You must be logged in to reply to this topic.