0

I have a questions regard what the difference is when accepting values through either a Pipeline, or Parameter input.

I ask because:

  • Accepting values by pipeline in my script, works.
  • Accepting values using the parameter argument such as, "-ComputerName" sort of works.

I say sort of because, It does take my input from the same variable up until theres one that it cant connect to, then it stops the script. Is there a reason for this?

Heres my script for anyone curious:

Function Set-DWRCVersion {
<#
NAME
    Set-DWRCVersion
    
SYNTAX
    Set-DWRCVersion [[-ComputerName] <string[]>]  [<CommonParameters>]

EXAMPLES
    Set-DWRCVersion -ComputerName LocalHost,ComputerOne
    'LocalHost','ComputerOne' | Set-DWRCVersion
    $CritNames | Set-DWRCVersion
    
    $CritNames | Set-DWRCVersion |Export-Csv "$($env:USERPROFILE.Substring(0,20))\Desktop\NewCrit.CSV" -Force -NoTypeInformation 
#>

[cmdletbinding()]
    Param(
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipeLineByPropertyName=$true)]
                   [ValidateLength(1,15)]
                   [Alias('Comp','CN','name')]
                   [string[]]$ComputerName)
                   
Process{
        try{
        [string]$Path = "C:\Users\Abraham\Desktop\dwrcs"  #Dameware File Path (current)
            foreach($Computer in $ComputerName){
                $PSSession = New-PSSession -ComputerName $Computer -ErrorAction Stop
                $TestPath  = Invoke-Command -Session $PSSession -ScriptBlock { Test-Path -Path "C:\Windows\dwrcs" }
                    if($TestPath -eq $false){
                        Copy-Item -Path $Path -Destination "\\$Computer\C$\Windows" -Recurse -Force }
                        #Start-Sleep -Seconds 1

                $EXEVersion = Invoke-Command -Session $PSSession -ScriptBlock { [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Windows\dwrcs\DWRCS.EXE").FileVersion }
                $DLLVersion = Invoke-Command -Session $PSSession -ScriptBlock { [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Windows\dwrcs\DWRCRSS.dll").FileVersion }
                    if($EXEVersion -notmatch '12.1.2.584'){
                        [PSCustomObject] @{
                                "Computer Name"  = $Computer
                                "Status"         = "Online"
                                "DWRC Version"   = $EXEVersion
                                "DWRCRSS DLL"    = $DLLVersion
                                "DWRC Check"     = "Not up to date" }
                                ""
                        Write-Output -InputObject "Version not current"
                        Write-Output -InputObject "Stopping Service. . ."
                        Invoke-Command -Session $PSSession -ScriptBlock { 
                            Stop-Service -Name dwmrcs;
                            Get-Item -Path C:\windows\dwrcs | Rename-Item -NewName "dwrcs.old" }
                            Remove-Item -Path "\\$Computer\c$\Windows\dwrcs.old" -Recurse -Force -ErrorAction SilentlyContinue
                            #Start-Sleep 1

                        Write-Output -InputObject "Copying new files over. . ."
                        Copy-Item -Path $Path -Destination "\\$Computer\C$\Windows" -Recurse -Force

                        Write-Output -InputObject "Starting Service. . ."
                        Invoke-Command -Session $PSSession -ScriptBlock { Start-Service -Name dwmrcs }
                              } 
                    elseif($EXEVersion -match '12.1.2.584') {
                        [PSCustomObject] @{
                                "Computer Name"  = $Computer
                                "Status"         = "Online"
                                "DWRC Version"   = $EXEVersion
                                "DWRCRSS DLL"    = $DLLVersion
                                "Version Check"  = "Up to Date" }
                              }
                    else { Write-Output -InputObject "Error Occured"
                           Throw "$($Error[0].Exception.Message)"  }
                    }
            } Catch {
                        [PSCustomObject] @{
                                "Computer Name"  = $Computer
                                "Status"         = "Offline"
                                "DWRC Version"   = $null
                                "DWRCRSS DLL"    = $null
                                "Version Check"  = $null }
            } Finally {
                if ($PSSession) {
                Get-PSSession | Remove-PSSession }
        }
    }
}

Overall, its a pretty simple script where it will get some remote file versions for me. I am just not sure why accepting values from the pipeline doesn't stop the script, but inputting them using the regular Parameter does.

Im importing from a csv to get the names using the following:

$Crit = Import-Csv -Path "$($env:USERPROFILE.Substring(0,20))\Desktop\med.csv"
$CritNames = $Crit.'NetBios Name' -replace "AREA51\\","" 

<# Names return like so:
COmputerOne
COmputerTwo
COmputerThr
Etc.. #>

And when running it using the pipeline input: $CritNames | Set-DWRCVersion which works but, Set-DWRCVersion -ComputerName $CritNames doesn't; well it does up until it hits an offline computer then stops the script.

Is there something im missing? Can someone much smarter than me edumacate me?(:

1 Answer 1

2

You can solve this by moving the try/catch/finally statement inside the foreach loop:

process {
  foreach($Computer in $ComputerName){
    try {
      # ...
    }
    catch {
      # ...
    }
    finally {
      # ...
    }
  }
}

Why would this make a difference?

When explicitly binding a value via -ComputerName, the process block is only invoked once - but when you supply input via the pipeline, the process block is invoked once per input item.

You can observe this behavior with a simple test function like this:

function Test-ProcessInvocation {
  param(
    [Parameter(Mandatory,ValueFromPipeline)]
    [string[]]$ComputerName
  )

  process {
    "Running process block with $($ComputerName.Length) input arguments: [$ComputerName]"
  }
}

Running this with both input modes will make this behavior clearly visible:

PS ~> Test-ProcessInvocation -ComputerName 1,2,3
Running process block with 3 input arguments: [1 2 3]
PS ~> 1,2,3 |Test-ProcessInvocation
Running process block with 1 input arguments: [1]
Running process block with 1 input arguments: [2]
Running process block with 1 input arguments: [3]

This means that running Set-DWRCVersion -ComputerName Computer1,Computer2 translates to this sequence of statements:

# Single invocation of `process` block with $ComputerName = all input values at once
try {
  foreach($Computer in 'Computer1','Computer2'){

  }
}
catch {}
finally {}

Running 'Computer1','Computer2' |Set-DWRCVersion on the other hand:

# `process` block repeats per input item
try {
  foreach($Computer in 'Computer1'){

  }
}
catch {}
finally {}

try {
  foreach($Computer in 'Computer2'){

  }
}
catch {}
finally {}

As a result, throwing an error inside the foreach loop when invoking via the pipeline never "skips" any items, because the loop is only ever operating on one at a time.

By inverting the relationship between the loop and the try/catch statement, any errors will now have been captured and handled inside the loop, and it will no longer skip the remaining input values in $ComputerName.

1
  • This makes complete sense to me, and was exactly what I was after. Thank you! Commented Apr 11, 2021 at 5:55

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