none
Powrshell adding certain objects from primary array into a secondary array and sorting

    Question

  • Hello,

    I seem to be running into a problem copying objects into a new array and trying to sort the new array of objects. I suspect it's some kind of deep copy / shallow copy thing. 

    Forgive me if I screwed up the code paste. I've never used it before.

    The 2 resultant arrays do not sort. I suspect the $records array is getting manipulated.

    Can you show me how to deep copy these system objects into the arrays so they will sort please?

    Note: I have a method that checks for valid ips (not shown) which works fine

    $records = Get-ADComputer -Filter {OperatingSystem -NotLike "Windows Server*"} -Property * | Select Name, OperatingSystem, LastLogonDate, IPv4Address
    
    #Create arrays for both record types
    $invalidRecords = @()
    $validRecords = @()
    
    foreach($record in $records){
    
           #Add records to the appropriate array
           if( (isValidIP($record.IPv4Address) ) -eq $false ){
           
              $invalidRecords += $record
                
           }else{
           
              $validRecords += $record
           
           } 
    
    
        }
    
    
        #Sorting on Names
        $invalidRecords | Sort-Object Name
        $validRecords | Sort-Object Name


    • Edited by mmurphy58 Wednesday, February 6, 2019 11:48 PM
    Wednesday, February 6, 2019 11:45 PM

Answers

  • I was following the examples from Microsoft here:

    https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/sort-object?view=powershell-6Thanks you for pointing out the issue.

    I'll work to understand this better, and get back. Thanks for pointing out the issue.

    You are still not understanding that adding sort after a collecttion does not sort the collection.  It just outputs a sorted result.  Sorting to the console does nothing to the original collection. That is how the pipeline works.  It is not the old DOS sort utilities which sort a file into another file.5

    $x = 10..1 
    $x | sort
    $x # not sorted
    $y = $x | sort 
    $y # sorted


    \_(ツ)_/

    • Marked as answer by mmurphy58 Friday, February 8, 2019 11:15 PM
    Friday, February 8, 2019 10:21 PM

All replies

  • What is this: "isValidIP"?

    \_(ツ)_/

    Thursday, February 7, 2019 12:11 AM
  • I mentioned in my comments its a method I created takes the IPv4Address and makes sure its in an address range we expect. we have specific internal IP's so if it returns 192.168.x.x or 172.x.x.x , or if it doesn't exist, it returns false.  This is all working properly.

    My Understanding after reading a bit is that you can not copy an object into a new array and expect it to be deep. It just references where it was in the original array and manipulates it there.

    • Edited by mmurphy58 Thursday, February 7, 2019 12:21 AM
    Thursday, February 7, 2019 12:18 AM
  • You are overcomplicating things. Try it this way:

    $records = Get-ADComputer -Filter "OperatingSystem -NotLike 'Windows Server*'" -Property Name, OperatingSystem, LastLogonDate, IPv4Address | Select-Object -Property Name, OperatingSystem, LastLogonDate, IPv4Address
    
    $validRecords = $records | Where-Object -FilterScript {$([IPADDRESS]$($_.IPv4Address))}
    $invalidRecords = $records | Where-Object -FilterScript {-not $([IPADDRESS]$($_.IPv4Address)) }

    Edit: I read your answer about "isValidIP" after my initial answer. Of course you can use your function in the Where-Object if ou like. As long as it's outputting $true or $false

    Edit #2: For performance you should add a -SearchBase to your query. It will probably speed up the query and will be less stressy for the AD controller.  ;-)


    Live long and prosper!

    (79,108,97,102|%{[char]$_})-join''




    • Edited by BOfH_666 Thursday, February 7, 2019 12:35 AM
    Thursday, February 7, 2019 12:29 AM
  • I mentioned in my comments its a method I created takes the IPv4Address and makes sure its in an address range we expect. we have specific internal IP's so if it returns 192.168.x.x or 172.x.x.x , or if it doesn't exist, it returns false.  This is all working properly.

    My Understanding after reading a bit is that you can not copy an object into a new array and expect it to be deep. It just references where it was in the original array and manipulates it there.

    Then one of the two arrays has to have entries.  YOu will have to debug the code to find out where your code is failing.

    Start with this and when you see how it works add you function into the code.

    $records = Get-ADComputer -Filter { OperatingSystem -NotLike "Windows Server*" } -Property OperatingSystem, LastLogonDate, IPv4Address | 
        Select-Object Name, OperatingSystem, LastLogonDate, IPv4Address
    
    $invalidRecords = @()
    $validRecords = @()
    
    foreach ($record in $records) {    
        #Add records to the appropriate array
        if ($record.IPv4Address) {        
            $validRecords += $record
        } else {
            $invalidRecords += $record
        }
    }
    #Sorting on Names
    $invalidRecords | Sort-Object Name
    $validRecords | Sort-Object Name


    \_(ツ)_/

    Thursday, February 7, 2019 12:36 AM
  • This is perhaps the easiest way to do this:

    filter isValidIP {
        Param([switch]$Invert)
        
        $validAddress = $true #...#validation code
        
        if($validAddress -and -not $Invert){$_}  # return when address bad
        if(-not $validAddress -and $Invert){$_}
    }
    $records = Get-ADComputer -Filter { OperatingSystem -NotLike "Windows Server*" } -Property OperatingSystem, LastLogonDate, IPv4Address |
        Select-Object Name, OperatingSystem, LastLogonDate, IPv4Address
    $validAddress = $records | isValidIP | Sort-Object Name
    $invalidAddress = $records | isValidIP -Invert | Sort-Object Name


    \_(ツ)_/

    Thursday, February 7, 2019 12:52 AM
  • Here is the full range test filter.  You can add as many ranges in the expression as needed.

    filter isValidIP {
        Param([switch]$Invert)
        
        $validAddress = ([ipaddress]$_).Address -gt ([ipaddress]'192.168.0.1').Address -and
                ([ipaddress]$_).Address -lt ([ipaddress]'192.168.0.255').Address
        
        if($validAddress -and -not $Invert){$_}  # return when address good - no invert
        if(-not $validAddress -and $Invert){$_}  # return when address bad and invert requestes
    }


    \_(ツ)_/


    • Edited by jrv Thursday, February 7, 2019 12:58 AM
    Thursday, February 7, 2019 12:57 AM
  • The code works fine. I end up with an array that has Name,OS,LastLogonDate,ValipIPs and I end up with an array that has Name,OS,LastLogonDate, and either null Ips or Ips i did not want such as 192.168.

    Both arrays end up with the composition I expect, but they will not sort. I think when you copy objects in this manner to a new array you are not actually copying them, you are creating a pointer (REF) back to the original array. If I sort the $records array (and not the 2 new arrays), it sorts fine.

    The reason for possible null or empty ip's is we have stale entries in AD were working on getting out. The reason I need to filter out other ip's such as 192.168, is because Get-ADComputer | IPv4Address has a bug in the code; It gets an array of Ip's (1 for each NIC) , but only returns the first one, so if it doesn't match the ip range we expect, I have to consider it it bad, write it to a different file, and use Resolve-DNSName cmdlet.

    Sorry, I'm a total nube.


    • Edited by mmurphy58 Thursday, February 7, 2019 1:45 AM
    Thursday, February 7, 2019 1:40 AM
  • They sort fine for me.  What makes you think they are not sorting?


    \_(ツ)_/

    Thursday, February 7, 2019 1:46 AM
  • So changing the way I query the data seems to work as you show above.(Select-Object)

    The arrays are properly sorted in the console window. Now the problem seems to be using Export-CSV witch it looks like it has had issues in the past.

    When I use the following code and open the files they are not completely sorted anymore. I've tried this with and without -NoTypeInformation

     $invalidRecords | Export-CSV -NoTypeInformation -Path $inValidFilePath
        $validRecords | Export-CSV -NoTypeInformation -Path $validFilePath


    • Edited by mmurphy58 Thursday, February 7, 2019 7:33 PM
    Thursday, February 7, 2019 7:16 PM
  • There is no reason for this so you are likely doing something in your code that we cannot see.

    filter isValidIP {
        Param([switch]$Invert)
        
        $validAddress = ([ipaddress]$_).Address -gt ([ipaddress]'192.168.0.1').Address -and
                ([ipaddress]$_).Address -lt ([ipaddress]'192.168.0.255').Address
        
        if($validAddress -and -not $Invert){$_}  # return when address bad
        if(-not $validAddress -and $Invert){$_}
    }
    $records = Get-ADComputer -Filter { OperatingSystem -NotLike "Windows Server*" } -Property OperatingSystem, LastLogonDate, IPv4Address |
        Select-Object Name, OperatingSystem, LastLogonDate, IPv4Address
    $records | isValidIP | Sort-Object Name | Export-Csv $validAddressFile
    $records | isValidIP -Invert | Sort-Object Name | Export-Csv $invalidAddressFile
    Works for me.


    \_(ツ)_/

    Thursday, February 7, 2019 9:16 PM
  • Well Here's all the code:

    # Get AD module
    import-module ActiveDirectory
    
    ################### Functions ########################
    
    ######################################################
    # isValidIp{}
    #
    # Determins whether an existing ip address is valid
    # Params: [string] $ip : the ip address to evaluate.
    #
    # return boolean
    #
    ######################################################
    
    function isValidIP{
    
        param([string]$ip)
        
        if (($ip)-and([ipaddress]$ip)){ 
    
            if( ($ip.Substring(0,3).Equals("10."))-or($ip.Substring(0,4).Equals("123.")) ){
            
                return $true
            }
    
        }
    
        return $false
    }
    
    
    $records = Get-ADComputer -Filter {OperatingSystem -NotLike "Windows Server*"} -Property Name, OperatingSystem, LastLogonDate, IPv4Address | Select-Object Name, OperatingSystem, LastLogonDate, IPv4Address
    
    #Create arrays for both record types
    $invalidRecords = @()
    $validRecords = @()
    
    
    #Check for valid or missing IP's 
    if($records){
    
        foreach($record in $records){
        
           #Add records to the appropriate array
           if( isValidIP($record.IPv4Address) ){
           
              $validRecords += $record
                
           }else{
              
              $invalidRecords += $record
           
           } 
        
        
        }
        
        
        #Sorting on Names
        $validRecords | Sort-Object Name
        $invalidRecords | Sort-Object Name
        
        
        $validFilePath = "C:\Users\$([Environment]::UserName)\Desktop\AD-Info-WValid-IP.csv"
        $invalidFilePath = "C:\Users\$([Environment]::UserName)\Desktop\AD-Info-NoValid-IP.csv"
        
        Write-Host "Writing files to Desktop"
        
        $validRecords | Export-CSV -Path $validFilePath -NoTypeInformation
        $invalidRecords | Export-CSV -Path $inValidFilePath -NoTypeInformation
        
    
    }else{
    
        Write-Host "Error: Unable to obtain AD Records."
    
    }

    Thursday, February 7, 2019 10:16 PM
  • I tried to copy and paste your code  and it returns an error for each iteration:

    Cannot convert the "@{Name=SomeComputerName; OperatingSystem=Windows 7 Professional; LastLogonDate=2/7/2019 11:45:03 AM; IPv4Address=xxx.xxx.xx.xxx}" value of
    type "Selected.Microsoft.ActiveDirectory.Management.ADComputer" to type "System.Net.IPAddress".
    At C:\Scripts\test.ps1:9 char:5
    +     $validAddress = ([ipaddress]$_).Address -gt ([ipaddress]'192.168. ...

    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
        + FullyQualifiedErrorId : ConvertToFinalInvalidCastException +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Since I really don;t understand what you're doing in your filter, I can;t really modify it. Please look at my code I posted and see if you can find the problem.

    Thanks


    • Edited by mmurphy58 Thursday, February 7, 2019 10:54 PM
    Thursday, February 7, 2019 10:53 PM
  • You are not sorting anything.  It is just outputting the results to the screen.

    This does not sort anything:

    #Sorting on Names
    $validRecords
    | Sort-Object Name
    $invalidRecords
    | Sort-Object Name

    Please look at how all of us have specified the sort.


    \_(ツ)_/

    Thursday, February 7, 2019 10:57 PM
  • I was following the examples from Microsoft here:

    https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/sort-object?view=powershell-6Thanks you for pointing out the issue.

    I'll work to understand this better, and get back. Thanks for pointing out the issue.

    Friday, February 8, 2019 9:33 PM
  • I was following the examples from Microsoft here:

    https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/sort-object?view=powershell-6Thanks you for pointing out the issue.

    I'll work to understand this better, and get back. Thanks for pointing out the issue.

    You are still not understanding that adding sort after a collecttion does not sort the collection.  It just outputs a sorted result.  Sorting to the console does nothing to the original collection. That is how the pipeline works.  It is not the old DOS sort utilities which sort a file into another file.5

    $x = 10..1 
    $x | sort
    $x # not sorted
    $y = $x | sort 
    $y # sorted


    \_(ツ)_/

    • Marked as answer by mmurphy58 Friday, February 8, 2019 11:15 PM
    Friday, February 8, 2019 10:21 PM

  • I think I see what you mean. I'll work with your example and get back. Thanks
    • Edited by mmurphy58 Friday, February 8, 2019 10:46 PM
    Friday, February 8, 2019 10:27 PM
  • Thanks for all your help and patience!

    This works fine as long as its piped as you said.

    #Sorting on Names and exporting
        $validRecords | Sort-Object Name | Export-CSV -Path $validFilePath -NoTypeInformation
        $invalidRecords | Sort-Object Name | Export-CSV -Path $inValidFilePath -NoTypeInformation

    This allows me to keep the code I already have in place without trying to change to Filter and $Invert which I don't even understand yet.

    BTW that Filter code throws errors :

    Cannot convert the "@{Name=XXXXX; OperatingSystem=Windows 7 Professional; LastLogonDate=2/7/2019 9:48:36 AM; IPv4Address=xxx.xxx.xx.xxx}" value of type
    "Selected.Microsoft.ActiveDirectory.Management.ADComputer" to type "System.Net.IPAddress".
    At C:\Scripts\another-test.ps1:9 char:5
    +     $validAddress = ([ipaddress]$_).Address -gt ([ipaddress]'10.0.0.1 ...
    +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
        + FullyQualifiedErrorId : ConvertToFinalInvalidCastException

    Friday, February 8, 2019 11:14 PM
  • Thanks for all your help and patience!

    This works fine as long as its piped as you said.

    #Sorting on Names and exporting
        $validRecords | Sort-Object Name | Export-CSV -Path $validFilePath -NoTypeInformation
        $invalidRecords | Sort-Object Name | Export-CSV -Path $inValidFilePath -NoTypeInformation

    This allows me to keep the code I already have in place without trying to change to Filter and $Invert which I don't even understand yet.

    BTW that Filter code throws errors :

    Cannot convert the "@{Name=XXXXX; OperatingSystem=Windows 7 Professional; LastLogonDate=2/7/2019 9:48:36 AM; IPv4Address=xxx.xxx.xx.xxx}" value of type
    "Selected.Microsoft.ActiveDirectory.Management.ADComputer" to type "System.Net.IPAddress".
    At C:\Scripts\another-test.ps1:9 char:5
    +     $validAddress = ([ipaddress]$_).Address -gt ([ipaddress]'10.0.0.1 ...
    +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
        + FullyQualifiedErrorId : ConvertToFinalInvalidCastException

    You have not copied it correctly as I noted before.  Run my code only changing the control strings until you understand how it works.  Do not use PowerShell v2 for this.  The code I posted works correctly.

    Once you understand how PowerShell works you can modify it to do what you like.

    To use PowerShell or to understand assistance you must first learn the basics of PowerShell. You are not there yet.  You are still at the "guessing" stage.  This is a good project to help you learn how it works.


    \_(ツ)_/

    Friday, February 8, 2019 11:28 PM
  • Okay Thanks a bunch
    Friday, February 8, 2019 11:40 PM