1

I'm currently working on a script to gather a whole lot of information from computers in a domain. As I want to show how far it has come along (the task may run for hours or minutes) I wanted to use Write-progress. Works fine, until I started passing the list of computers via the pipeline and not as "normal" parameters..

Consider this code:

Function Test-SomethingOnAllComputers {
[CmdletBinding()]
param (
    [parameter(
        valuefrompipeline=$true,
        ValueFromPipelineByPropertyName=$true)]
        [Alias("DNSHostName")
        [string[]]$ComputerName
    )
BEGIN {
## Do some init. stuff
    Write-progress -PercentComplete 0 -Status "Init..."
    $i = 1
}

PROCESS {
# Do something with each computername passed in.
Write-Progress -PercentComplete ($Input.Count/$i*100) -Status "Doing stuff to $ComputerName"
}

END {
Write-Progress -PercentComplete 100 -status "Done with all objects in the pipeline. Finalizing..."
# Clean up and complete the job.. 
Write-Progress -PercentComplete 100 - Status "Finished.." -Completed
}
}

Usage: Get-ADComputer -Filter {Name -like "Prod*"} | Test-SomethingOnAllComputers

I get two questions/issues here:

  1. The data I get from Get-ADComputer is DNSHostName and not ComputerName (hence the alias), but it does not seem like it gets picked up when I refer to ComputerName in the PROCESS part. If I change the parameter to be DNSHostName I get the correct value. So I do guess I'm just missing something very relevant here with the pipeline.

    • This is solved by doing (and yes, thanks Microsoft, that looks very nice...):

    Get-ADComputer -Filter { Name -like "Prod*"} | % {[string[]]$_.DNSHostName}| Test-SomethingOnAllComputers

    Thanks to jbsmith for clearing this up

  2. I'm not able to get the total count of objects passed down the pipeline. Is there a way to do that without looping though the entire input object pipeline?

2
  1. This is a known issue with objects returned from the AD cmdlets. If you request a property from them that they do not have, they will automatically create it as $null. It's annoying, non-standard behavior that confused me too when first piping objects into my own PowerShell functions. So PowerShell tries to bind the ComputerName property first, and gets $null, so it never tries the alias. Just swap the alias and the actual parameter name, and things will work as you expect. I do this exact thing for all my ComputerName parameters for just this reason. This is NOT typical, and what you are trying to do would work with essentially any other cmdlets.

I'll let someone else handle your second question -- I don't know of a good way to do it while preserving the ability to pipeline into your function easily.

  • Yeah, that's really bad comming from Microsoft. Why on earth could't they make their own functions adhere to their own standards?:-) This is working: Get-ADComputer -Filter { Name -like "OSL*D"} | % {[string[]]$_.DNSHostName}| Test-SomethingOnAllComputers – mdavidsen Sep 25 '14 at 11:45
1

This does not seem to be a general pipeline bug, but rather a quirk with the AD Cmdlets and the way the Properties loaded from Active Directory is populated.

If you pipe the entire Computer object as Objects (cast using [Object[]] in your param block) instead, and inspect the Properties of the object with $_.psobject.Properties, you should see that internally, the Object passed to the Test- cmdlet has all the AD Attributes stored in a single array property called Properties (yay for naming confusion) and the "properties" that you see when you pipe the object to Get-Member are really dynamic and populated from this array.

A more elegant way to ensure that the DNSHostName property is populated and working when piping the computer objects would be:

Get-ADComputer -Filter * | Select-Object -Property DNSHostName | Test-SomethingOnAllComputers

or

Get-ADComputer -Filter * | Select-Object -Property * | Test-SomethingOnAllComputers

This should force the properties into persistence if you will, just like your foreach example.


With regards to Write-Progress, the issue is that when piping from another Cmdlets, you can't possible know how many objects to expect when you start processing the first block.

If you want to use Write-Progress -PercentCompleted you'll need to first load all of your computers into a collection and then run the Test- cmdlet, effectively defeating your Pipelining efforts:

$Computers = Get-ADComputer -Filter * |Select-Object -ExpandProperty DNSHostName
Test-SomethingOnAllComputers -ComputerName $Computers

And then have something like this in the Begin block:

$ComputerCount = $ComputerName.Count
$i = 0

Now you should be able to do

$i++
Write-Progress -Activity "Doing Stuff" -Status "Doing stuff to $ComputerName" -PercentComplete $($i/$ComputerCount * 100)

Don't do this with pipeline input, as $ComputerCount would be zero and only Chuck Norris can divide by zero

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.