Can't Remove ArrayList Item

This topic contains 7 replies, has 5 voices, and was last updated by  Dan Potter 9 months, 1 week ago.

  • Author
    Posts
  • #61839

    Aaron Hardy
    Participant

    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"
    
  • #61842

    Olaf Soyk
    Participant

    ... and it appears my code should work.

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

  • #61845

    Aaron Hardy
    Participant

    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
    
    }
    • #61858

      Olaf Soyk
      Participant

      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.

  • #61869

    Ron
    Participant

    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))
  • #61873

    Rob Simmers
    Participant

    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
    
  • #61909

    Aaron Hardy
    Participant

    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.

  • #61912

    Dan Potter
    Participant

    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
    

You must be logged in to reply to this topic.