Piping computers to Get-Service

This topic contains 10 replies, has 4 voices, and was last updated by Profile photo of Rob Simmers Rob Simmers 2 years, 2 months ago.

  • Author
    Posts
  • #25174
    Profile photo of Michael Hammond
    Michael Hammond
    Participant

    Hi! There's a bit of behavior I've never quite understood, and I'm hoping someone can shed some light on this part of the pipelining process. Here's my stream-of-consciousness thought process.

    First idea:
    Get-ADComputer -filter * | Get-Service
    This doesn't work, of course, because Get-Service is expecting computers to be described by a ComputerName property, and Get-ADComputer produces objects that store that data in a Name property.

    Second idea:
    Get-ADComputer -filter * | Select-Object @{Name="ComputerName"; Expression={$_.Name} | Get-Service
    This also fails, but I'm not clear on why. From the error message, it looks as though the result of the Select-Object is being picked up by the -Name parameter. I'm theorizing that the pipelined input looks like an array of strings to Get-Service, and -Name picks them up ByValue.

    Am I in the neighborhood? I have seen that adding a manual -Name * to the Get-Service call solves the issue, allowing the incoming data to be consumed ByPropertyName, but I'm more interested in understanding what PowerShell is thinking when that has NOT been done.

    Thanks in advance for the insights.

  • #25178
    Profile photo of Rob Simmers
    Rob Simmers
    Participant

    This was an interesting puzzle and I'll be curious to see if anyone else adds some insight. We look at the parameters:

    PS C:\> Get-Help Get-Service -Parameter Name
    
    -Name 
        Specifies the service names of services to be retrieved. Wildcards are permitted. By default, Get-Service gets all of the services on the computer.
        
        Required?                    false
        Position?                    1
        Default value                All services
        Accept pipeline input?       true (ByPropertyName, ByValue)
        Accept wildcard characters?  true
        
    
    
    
    
    PS C:\> Get-Help Get-Service -Parameter ComputerName
    
    -ComputerName 
        Gets the services running on the specified computers. The default is the local computer.
        
        Type the NetBIOS name, an IP address, or a fully qualified domain name of a remote computer. To specify the local computer, type the computer name, a dot (.), or "localhost".
        
        This parameter does not rely on Windows PowerShell remoting. You can use the ComputerName parameter of Get-Service even if your computer is not configured to run remote commands.
        
        Required?                    false
        Position?                    named
        Default value                Local computer
        Accept pipeline input?       true (ByPropertyName)
        Accept wildcard characters?  false
    

    Observations are Name is ByPropertyName or ByValue and ComputerName accepting ByPropertyName. Both parameters have Default values. The error message shows:

    PS C:\> Get-ADComputer MyPuter| Select @{Label="ComputerName";Expression={$_.NAME}} | Get-Service
    
    Get-Service : Cannot find any service with service name '@{ComputerName=MyPuter}'.
    At line:1 char:86
    + ... n={$_.NAME}} | Get-Service
    +                    ~~~~~~~~~~~
        + CategoryInfo          : ObjectNotFound: (@{ComputerName=MyPuter}:String) [Get-Service], ServiceCommandException
        + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand
     
    

    I test this with Get-Process and it works fine. I built a function with I would assume is the same logic in Get-Service:

    function Test-This{
        param(
            [Parameter(ValueFromPipelineByPropertyName=$true)]
            [ValidateNotNullOrEmpty()]
            [string[]]
            $ComputerName = "Local Computer",
            [Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true)]
            [ValidateNotNullOrEmpty()]
            [string[]]
            $Name = "All Services"
        )
        begin{}
        process{
            foreach ($computer in $computername) {
                "Processing {0}" -f $computer
                foreach ($service in $name) {
                    "Processing {0} in {1}" -f $service, $computer
                }
            }
        }
        end{}
    }
    

    So, now we try the same logic and pass it to this function:

    PS C:\> Get-ADComputer -Filter * | Select @{Label="ComputerName";Expression={$_.NAME}}  -First 3 | Test-This
    Processing MyServer1
    Processing @{ComputerName=MyServer1} in MyServer1
    Processing MyServer2
    Processing @{ComputerName=MyServer2} in MyServer2
    Processing MyServer3
    Processing @{ComputerName=MyServer3} in MyServer3
    

    It appears that because Name is stipulated as ByValue that since data is being piped to it that it overrides what is set as the default parameter and attempts to use the piped data. See how it's searching for a service name of "@{ComputerName=MyServer1}" just like the error states above. If we add the Name="*", it works because now the pipe contains a value for Name and doesn't use the data from the pipe line:

    PS C:\> Get-ADComputer -Filter * | Select @{Label="ComputerName";Expression={$_.NAME}}, @{Label="Name";Expression={"*"}}  -First 3 | Test-This
    Processing MyServer1
    Processing * in MyServer1
    Processing MyServer2
    Processing * in MyServer2
    Processing MyServer3
    Processing * in MyServer3
    

    I think that explains the behavior, but I would like to know the proper way to actually fix it. What is different in Get-Process versus Get-Service? I tried putting this:

    if (!($PSBoundParameters.ContainsKey('Name'))) {$Name="*"}
    

    In the begin and process blocks but it still uses the piped data. Hopefully someone can tell us the proper ways to handle and override this behavior. I'll research more later, but gotta get some work done.

  • #25193
    Profile photo of Martin Nielsen
    Martin Nielsen
    Participant

    The default pipeline variable in Get-Service is -Name, meaning you can't pipe a ComputerName into Get-Service. You pipe a service name, like

    'wuauserv' | Get-Service
    
    Status   Name               DisplayName
    ------   ----               -----------
    Stopped  wuauserv           Windows Update
    

    What you can do is flip it on its head, and do

    Get-Service -ComputerName (Get-ADComputer -Filter { Name -like 'HYPERV*' } | Select-Object -ExpandProperty Name)
    

    But it's not very pretty since Get-Service doesn't return the ComputerName, and Get-Service with -ComputerName uses the oldschool RPC protocol to query for data thanks to it being one of the very first commands introduced to PowerShell and never having been updated since. Instead I'd do this

    Invoke-Command -ComputerName (Get-ADComputer -Filter { Name -like 'HYPERV*' } | Select-Object -ExpandProperty Name) -ScriptBlock { Get-Service }
    
  • #25194
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Just for giggles, what happens if you do this?

    Get-ADComputer -filter * | Select-Object @{Name="ComputerName"; Expression={ [string]$_.Name } | Get-Service
    

    Parameter binding tries to do things without coercion first (where the piped object's value or property exactly matches the type of the parameters), and only then falls through to trying type coercion. From your error message, it looks like you're making it all the way to the ByValue (With Coercion) binding, so maybe forcing your ComputerName property to be a String object will help.

    Also, the AD cmdlets are a pain. Their output objects behave very strangely, which sometimes leads to these types of problems in the pipeline. Whoever wrote that module was way too clever for their own good, and it leads to all sorts of gotchas. (Did you know that every single parameter in the AD cmdlets is dynamic? Why? Beats the heck out of me, but try creating a proxy function for one sometime, and marvel at the empty param() block.)

    • #25199
      Profile photo of Martin Nielsen
      Martin Nielsen
      Participant

      Just for giggles, what happens if you do this?

      Two things happened for me

      PS C:\Users\mni> Get-ADComputer -Filter { Name -like 'HYPERV*' } | Select-Object @{Name="ComputerName"; Expression={ [string]$_.Name } } | Get-Service
      Get-Service : Cannot find any service with service name '@{ComputerName=HYPERV2}'.
      At line:1 char:123
      + ... ]$_.Name } } | Get-Service
      +                    ~~~~~~~~~~~
          + CategoryInfo          : ObjectNotFound: (@{ComputerName=HYPERV2}:String) [Get-Service], ServiceCommandException
          + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand
      

      Secondly, the command made my SCVMM console commit suicide. Not entirely sure why.

  • #25201
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    OK, one more attempt to make the AD cmdlet behave:

    Get-ADComputer -filter * | Select-Object * | Select-Object @{Name="ComputerName"; Expression={ [string]$_.Name } | Get-Service
    

    Piping to Select-Object * looks ridiculous, but it's one of the common workarounds to make the AD cmdlets work well in a pipeline.

  • #25203
    Profile photo of Martin Nielsen
    Martin Nielsen
    Participant

    Still no dice, at least in my case. I'm pretty sure Get-Service won't take anything but a service name or a service object. Heck, even an explicit [pscustomobject]@{ ComputerName = 'HYPERV01' } doesn't work.

    This does however:

    PS C:\Users\mni> $service = Get-Service -ComputerName 'HYPERV01' -Name 'wuauserv'
    PS C:\Users\mni> $service
    
    Status   Name               DisplayName
    ------   ----               -----------
    Stopped  wuauserv           Windows Update
    
    
    PS C:\Users\mni> $service | Get-Service
    
    Status   Name               DisplayName
    ------   ----               -----------
    Stopped  wuauserv           Windows Update
    
  • #25204
    Profile photo of Rob Simmers
    Rob Simmers
    Participant

    @dave – Tried the cast to string, still doesn't work. I can do the same thing to Get-Process and it works.

    @Martin – Your just creating a string array in your example, but don't understand why you would not be able to pipe to the object. When I pipe to Get-Process, it works. If I look at it's properties, it doesn't have Name as ByValue, but there is InputObject setup for ByValue. Also, your examples are missing a end paren in the ComputerName examples.

    I think it's an interesting question. I reproduced the behavior in the function above, so I would be curious on how it would be handled. If it's not an accurate portrayal of how the cmdlet would function, I'd be curious to know what is wrong with the implementation in the function above.

    PS C:\> Get-ADComputer MyComputer | Select @{Label="ComputerName";Expression={[string]$_.NAME}} | Get-Service
    
    Get-Service : Cannot find any service with service name '@{ComputerName=MyComputer}'.
    At line:1 char:94
    + ... ng]$_.NAME}} | Get-Service
    +                    ~~~~~~~~~~~
        + CategoryInfo          : ObjectNotFound: (@{ComputerName=MyComputer}:String) [Get-Service], ServiceCommandException
        + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand
     
    
    PS C:\> Get-ADComputer MyComputer | Select @{Label="ComputerName";Expression={[string]$_.NAME}} | Get-Process
    
    Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName                                                                                                                                       
    -------  ------    -----      ----- -----   ------     -- -----------                                                                                                                                       
        259      35     5160       6352   100            2620 agent                                                                                                                                             
         81       7     1164       4204    44            1164 armsvc                                                                                                                                            
        523      16    12448      16304    81   636.47   7452 audiodg  ....
    
  • #25205
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Okay, just played around with this a bit on my system. This fails:

    [pscustomobject]@{ComputerName = 'localhost'} | Get-Service
    

    It fails because the input object winds up binding to both ComputerName AND Name, which is annoying. This, on the other hand, works fine:

    [pscustomobject]@{ComputerName = 'localhost'} | Get-Service -Name *
    

    By binding the -Name parameter to * on the command line, we prevent the pipeline object from giving that parameter junk data. I'm not sure how much of the previous attempts you'll need to keep with regards to the objects coming out of the AD module, but in combination with adding -Name *, you should be able to get it working.

  • #25206
    Profile photo of Martin Nielsen
    Martin Nielsen
    Participant

    Well as I said, Get-Service was one of, if not [u]the[/u] first command in PowerShell, and it hasn't been touched since it was introduced. It's very likely that it's just poorly written by todays standards.

    Edit: Oh interesting. Maybe I'm wrong and the command is just funky?

  • #25207
    Profile photo of Rob Simmers
    Rob Simmers
    Participant

    @dave – Is there anything wrong in the function above? Should ByValue be removed? I see the same behavior and am just curious if it's Powershell behavior or bad parameter setup. Where would you check and override if the "Name" parameter is NULL and reset the global value?

You must be logged in to reply to this topic.