One of the great things about PowerShell is how easy and clear much of it is. But then there’s the other things that seem like magic. One of those is when you are dealing with objects that have properties that are collections of other objects.
Here will look at three solutions for dealing with properties that are collections of other objects:
- Using -ExpandProperty (and a custom column or two)
- Using ForEach (and a couple variables)
- Using ForEach (and a customized object)
Get-DependentServices
Sorry, there’s not such a cmdlet. Get-Service does return service objects that have a dependent services property. The only problem is that it returns a summary of the dependent services, and only a partial list. While you could pipe this to Format-List -Wrap, that will not give you any details about the dependent services.
Using -ExpandProperty
You can use -ExpandProperty to expand any single property.
While the above works nicely for a single service, it creates a mess for a collection of services. Which DependentService belongs to which service?
Your first attempt to solve this will probably fail. In this case, it fails as both the Service object has a Name property and the DependentServices objects have a Name property.
Your second attempt may run without error, but we still can’t see which Service belongs with which DependentService. Adding a custom column seems to be ignored here:
This didn’t work as expected because a Service object has a default list of Select properties defined in an XML file somewhere. ( :-) ) Your custom column was passed through the pipeline, but as a NoteProperty (a PowerShell added custom property).
You will need to pipe the last attempt to another Select to see your NoteProperty.
So the steps are:
- Create a custom column with the data to pass on down the pipeline.
@{n="ParentServiceName";e={$_.Name}} - Expand the property with the collection of child objects.
-ExpandProperty DependentServices - Pipe the child object and its new NoteProperty to a final Select.
select ParentServiceName, Status, Name
Using ForEach with Variables
ExpandProperty works great when you need to expand a single property. When you need to futher manipulate the data before sending it on down the pipeline you may want to use ForEach, formally ForEach-Object.
Get-Service | ForEach { some code } | Select ….
As a simple ForEach, let’s list Services in color based on Status.
Get-Service | foreach {
if ($_.Status -eq "Running")
{ Write-Host $_.Name -ForegroundColor Green } else
{ Write-Host $_.Name -ForegroundColor Red }
}
Write-Host does not add its content to the pipeline, so that’s the end of the line for this one.
Using ForEach to solve our Service and DependentServices problem, we will create and then reuse a variable that contains the name of the service. (I call this “Store and Forward”.) The ForEach code block can contain one or many statements seperated by semicolons. The last object returned in the code block is forwarded into the pipeline.
Get-Service | ForEach { $parent = $_.Name; $_.DependentServices } |
Select {$parent}, Status, Name
This collects one or more pieces of data from the parent object (the Service) as variables, and then passes the DependentServices object on through the pipeline. On the other side of the pipeline you can continute expanding objects and using the variables as needed. Here’s the Service and DependentServices solution using “Store and Forward”.
Using ForEach with Custom Objects
You can customize objects by adding “NoteProperties” to the object. You saw one form of this when we used the -ExpandProperty while listing other properties.
As a fun example, lets create a custom object that looks like a Service object, but has two properties named RunningDependentProperties and NonRunningDependentProperties.
We will need:
- A variable for the parent Service
- Two variables to store the child services (Empty arrays created as $r=@().)
- The Add-Member cmdlet to add a new NoteProperty to the Service object
Get-Service |
foreach {
# initialize the arrays
$r = @();
$nr = @();
# process all of the DependentServices
($_.DependentServices |
foreach { if ($_.status -eq "Running") { $r += $_ } else {$nr += $_ } }
); `
# attach the results to the original service
Add-Member -InputObject $_ -name RunningDependentServices -Value $r -MemberType NoteProperty;
Add-Member -InputObject $_ -name NonRunningDependentServices -Value $nr -MemberType NoteProperty;
$_
} |
Select name, RunningDependentServices, NonRunningDependentServices
So what’s it good for?
How about a list of only services with running dependent services? Just add a WHERE before the SELECT:
where { $_.RunningDependentServices.Count -gt 0 }
Or, how about a list of all of the services where the DependentService “applockerfltr” is not running? Just add a WHERE before the SELECT:
where { "applockerfltr" -in $_.NonRunningDependentServices.Name }
(If you are not reading this at http://TechTrainingNotes.blogspot.com… you are reading stolen content!)
Tip:
In addition to the expansion of properties that are collections, -ExpandProperty can also be used to return a simple datatype like a string or a number. By default, a Select returns a collection. In the example below note that a basic Select Status returns a property named “Status” that contains the value “Running”. If you pipe that to Get-Member you will see that it is still a ServiceController object. If you expand the property Status you will then get the simple string value of the property.
.