Author Posts

January 12, 2017 at 8:22 pm

I have an ArrayList of computers where I want to test against each one, and remove failed ones from this list. The logic works up until removing the item from the ArrayList.

Failed computers are added to a second ArrayList, then iterated through to remove its computers from the first ArrayList; otherwise I get the error about iterating through an ArrayList and invoking Remove() on it at the same time. I've scoured many online resources and PS books for ideas, and it appears my code should work. Scope issue?

[System.Collections.ArrayList]$ComputerName = @('PC1','PC2','PC3')
[System.Collections.ArrayList]$RemovalList = @()


foreach ($Computer in $ComputerName) {

    try {

        Test-WSMan -ComputerName $Computer -ErrorAction Stop | Out-Null

    } catch {

        $RemovalList.Add($Computer) | Out-Null

    }

    Write "`r`n"

}


foreach ($Computer in $RemovalList) {

        Write "Removing $Computer"
         
        $ComputerName.Remove($Computer)
}

Write "`r`n"
Write "Removed Computers:`r"
Write-Output $RemovalList
Write "`r`n"

Write "Accessible Computers:`r"
Write-Output $ComputerName
Write "`r`n"

January 13, 2017 at 12:30 am

... and it appears my code should work.

It does. At least for me. I don't get any error.

January 13, 2017 at 12:55 am

Thanks, Olaf. Like you, I did not get any errors. The problem is that the specified $Computer is not removed from the ArrayList when invoking Remove($Computer).

In the meantime, I did a workaround. I use [string[]]$ComputerName as the cmdlet parameter for getting the computer names, then 2 ArrayLists; one for accessible computers, and one for failed, then pass them to a custom object.

It's not very efficient but seems to work.

Function Get-WsManStatus {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
                    Position = 1)]
        [ValidateCount(1,32)]
        [string[]]$ComputerName
    )

    Write-Verbose "Initiating instance of System.Collections.ArrayList for AccessibleComputers."

    [System.Collections.ArrayList]$AccessibleComputers = @()


    Write-Verbose "Initiating instance of System.Collections.ArrayList for RemovedComputers."

    [System.Collections.ArrayList]$RemovedComputers = @()



    foreach ($Computer in $ComputerName) {

        $Computer = $Computer.ToUpper()

        Write-Verbose "Test-WsMan against $Computer"

        try {

            Test-WSMan -ComputerName $Computer -ErrorAction Stop | Out-Null

            Write-Verbose "$Computer OK."

            $AccessibleComputers.Add("$Computer") | Out-Null

        } catch {

            Write-Verbose "Test failed. Removing $Computer"

            $RemovedComputers.Add("$Computer") | Out-Null

        }

        Write "`r"
    }


    $HashTable = @{`
                AccessibleComputers = $AccessibleComputers;
                RemovedComputers = $RemovedComputers }

    $WsManStatusObj = New-Object -TypeName psobject -Property $HashTable

    Write-Output $WsManStatusObj

}

January 13, 2017 at 7:49 am

The problem is that the specified $Computer is not removed from the ArrayList when invoking Remove($Computer).

But that was it what I wanted to say. Besides I didn't get any errors it was working just as expected.
Probably you have a good reason for using arraylists instead of arrays. But another proper way to deal with such a challenge is to recreate the array without the elements you want to remove.

January 13, 2017 at 1:53 pm

Works properly for me too. Maybe check your version of .NET?

Remove           Method                string Remove(int startIndex, int count), string Remove(int startIndex)  

It's expecting an int for the index to remove. There's some behind the scenes magic going on to convert it for us.

You could try this and see if it works better.

$ComputerName.RemoveAt($ComputerName.IndexOf($Computer))

January 13, 2017 at 2:20 pm

Personally, I think your function is returning way to generic information. You get a list of computers that failed. Why did they fail? Next, you are going to loop through those computers to investigate what happened. Returning a list of computers doesn't get you much in either instance, it's not "Powershell-y".

Take a look at this:

function Get-WsManStatus {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
                    Position = 1)]
        [ValidateCount(1,32)]
        [string[]]$ComputerName
    )
    begin{}
    process {
        $results = foreach ($Computer in $ComputerName) {
            $Computer = $Computer.ToUpper()

            try {
                Write-Verbose ("Validating WsMan functionality enabled on {0}" -f  $Computer)
                $test = Test-WSMan -ComputerName $Computer -ErrorAction Stop
                Write-Verbose ("WSMan working on {0}" -f  $Computer)
                $props = @{
                    ComputerName = $Computer
                    Status = "SUCCESS"
                    Message = $test
                }

            } catch {

                Write-Verbose ("WSMan test failed on {0}" -f  $Computer)

                $props = @{
                    ComputerName = $Computer
                    Status = "FAILED"
                    Message = $_
                }

            } #try wsman

            New-Object -TypeName PSObject -Property $props
        } #foreach computer
    }#process
    end {
        $results
    }
} #function Get-WsManStatus

Get-WSManStatus -ComputerName "BadComputer", "GoodComputer", "AnotherGoodComputer"

Now you can use Group-Object, Where clauses, Sort and other analytics to tell you what failed and WHY it failed (e.g. not reacheable, WSMan failure specfics, etc.). Run the function against some computers and then do:

Get-WSManStatus -ComputerName "BadComputer", "GoodComputer", "AnotherGoodComputer" |
Group-Object -Property Status |
Sort-Object -Property Count -Descending

January 13, 2017 at 5:06 pm

Thank you Olaf, Ron and Rob, for your excellent feedback.

While the same code I posted yesterday didn't work, this morning it decided to work (no changes).

Rob – Appreciate the elaborate response; definitely a more effective approach. Being a new function, I was trying to get the core function working before tackling all the extras (why it failed, accept from pipeline, etc). Again, your example is much cleaner.

All the best.

January 13, 2017 at 6:11 pm

I'm with Rob.

This is something I would file away in the one off category.

$wsmanstatus = "BadComputer", "Goodcomputer", "AnotherGoodComputer" | % {

[pscustomobject]@{

Computer = $_
Status = ''

}

}


#before  
$wsmanstatus


$wsmanstatus | %{

$_.status = [bool](Test-WSMan -ComputerName $_.computer -ea silentlycontinue)

}

#after
$wsmanstatus