Passing a single item array causes an error but multiple item works

MrFlinstone 581 Reputation points
2020-12-23T23:02:59.97+00:00

I have the file below, which accepts an array of servers. Works well when there are multiple items in the array, and I note the answer that a user suggested in a previous post, which was to put a , in front of the values if its a single item array, trying that with a hash table just doesn't worked. I have tried various options without much luck. Custom classes would have been another option in a case like this, but as the script will use workflows, I believe custom classes cannot be used within a workflow.

param ([array[]]$servers = $(throw "Input array of servers.") , $folder )

$x = $servers
$k = 'serverid','servername','locationid','appid'    # key names correspond to data positions in each array in $x
 $h = @{}


 For($i=0;$i -lt $x[0].length; $i++){
     $x |
         ForEach-Object{
             [array]$h.($k[$i]) += [string]$_[$i]
         }


 } 

 $all_server_ids = $h['Serverid']

 foreach ($server_id in $all_server_ids)
{
    $severid = $h["serverid"][$all_server_ids.indexof($server_id)]
    $servername =  $h["servername"][$all_server_ids.indexof($server_id)]
    $locationid =  $h["locationid"][$all_server_ids.indexof($server_id)]

    Write-Output "This $severid and this $servername and this $locationid"

}

Running the below. .\test.ps1 -servers ,('72','Server1\DEV2','1.0') -folder "F:\files"

Cannot index into a null array.
+     $servername =  $h["servername"][$all_server_ids.indexof($server_i ...
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

Cannot index into a null array.
+     $locationid =  $h["locationid"][$all_server_ids.indexof($server_i ...
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

Please note that the script file will need to be able to deal with multiple arrays or a single one, however I can alter the way the parameter is passed for single item array if need be.

Windows Server PowerShell
Windows Server PowerShell
Windows Server: A family of Microsoft server operating systems that support enterprise-level management, data storage, applications, and communications.PowerShell: A family of Microsoft task automation and configuration management frameworks consisting of a command-line shell and associated scripting language.
5,576 questions
{count} votes

Accepted answer
  1. MotoX80 34,686 Reputation points
    2020-12-24T17:47:40.807+00:00

    a user suggested in a previous post, which was to put a , in front of the values if its a single item array,

    I had to put the comma after the values. You need to build the $x array dynamically.

    cls
    .\test.ps1 -servers ('72','Server1\DEV2','1.0') -folder "F:\files"
    .\test.ps1 -servers (('72','Server1\DEV2','1.0'),('88','Server8\DEV7','2.0')) -folder "F:\files"
    .\test.ps1 -servers ('72','Server1\DEV2','1.0'), -folder "F:\files"
    

    Updated code.

     param ([array[]]$servers = $(throw "Input array of servers.") , $folder )
     "-------------------------------"
     [System.Collections.ArrayList]$x =  @()               # start with an empty array 
     "Parameter count is {0}" -f $servers.count
     $AddServers = $false                                 # flag to add entire servers array 
     $servers | foreach {
        "This entry contains {0} items. " -f $_.count 
        if ($_.count -eq 1) {
            "Unable to process single dimentional array"
            $AddServers = $true 
        } else {
            $itemcount = $x.add($_)
        }
    
     }
    
     if ($AddServers -and $x.count -eq 0) {              # add a single entry but only if we haven't already added other entries
         "Adding entire servers arry."
         $itemcount = $x.add($servers)
     }
     "X count is {0}" -f $x.count
     if ($x.count -eq 0) {
        "Nothing to process"
        exit
     }   
     #$x = $servers                # this update builds the x array dynamically 
     $k = 'serverid','servername','locationid','appid'    # key names correspond to data positions in each array in $x
      $h = @{}
    
    
      For($i=0;$i -lt $x[0].length; $i++){
          $x |
              ForEach-Object{
                  [array]$h.($k[$i]) += [string]$_[$i]
              }
    
    
      } 
    
      $all_server_ids = $h['Serverid']
    
      foreach ($server_id in $all_server_ids)
     {
         $severid = $h["serverid"][$all_server_ids.indexof($server_id)]
         $servername =  $h["servername"][$all_server_ids.indexof($server_id)]
         $locationid =  $h["locationid"][$all_server_ids.indexof($server_id)]
    
         Write-Output "This $severid and this $servername and this $locationid"
    
     }
    

    Output:


    Parameter count is 3
    This entry contains 1 items.
    Unable to process single dimentional array
    This entry contains 1 items.
    Unable to process single dimentional array
    This entry contains 1 items.
    Unable to process single dimentional array
    Adding entire servers arry.
    X count is 1

    This 72 and this Server1\DEV2 and this 1.0

    Parameter count is 2
    This entry contains 3 items.
    This entry contains 3 items.
    X count is 2
    This 72 and this Server1\DEV2 and this 1.0

    This 88 and this Server8\DEV7 and this 2.0

    Parameter count is 2
    This entry contains 3 items.
    This entry contains 1 items.
    Unable to process single dimentional array
    X count is 1
    This 72 and this Server1\DEV2 and this 1.0

    PS C:\Temp>

    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Ian Xue 38,466 Reputation points Microsoft Vendor
    2020-12-24T13:34:03.42+00:00

    Hi,

    Just a workaround

    if($servers[0].count -eq 1){ $x = ,($servers)}  
    else{$x=$servers}   
    

    Best Regards,
    Ian Xue

    ============================================

    If the Answer is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


  2. Rich Matheisen 47,391 Reputation points
    2020-12-26T16:22:08.543+00:00

    How about just removing the cast of $servers from the parameter block and do your own checking on the parameter type and also validate the number of element of each array in the process?

    The reason the original code fails when you submit a single array is that when piping an array each element is passed into the pipe alone. So for an single array of four elements you'd have $x[0], $x[1],$x[2], and $x[3] going into that "[array]$h.($k[$i]) += [string]$_[$i]" code. And when you index a string, you get one character. So, instead of, say, '72' you'd get '7'.

    This no longer uses a pipeline. See if it works better. It's been packaged as a function and includes a series of tests at the end (just remove those!).

    # this is just a bonus to make the output look nice  
    function Ordinal{  
        [CmdletBinding()]  
        param (  
            [Parameter(mandatory)]  
            [int64]  
            $Cardinal  
        )  
        if ($cardinal -lt 0){ Throw "Cardinal must be positive"}  
        $lasttwodigits = $cardinal % 100  
        $lastdigit = $cardinal % 10  
        $suffix = switch ($lastdigit)  
                    {  
                                1 {'st'; break}  
                                2 {'nd'; break}  
                                3 {'rd'; break}  
                        default {'th'; break}  
                    }  
        if (11 -le $lasttwodigits -and $lasttwodigits -le 13){  
            $suffix = 'th'  
        }  
        "{0}{1}" -f $Cardinal, $suffix  
    }  
      
    # the real code starts here  
    function stuff{  
        param (  
            $servers = $(throw "Input array of servers.") ,   
            $folder   
        )  
              
        $x = $servers  
        $k = 'serverid','servername','locationid','appid'    # key names correspond to data positions in each array in $x  
        $h = [ordered]@{}  
      
        # manage single array passed as $servers parameter  
        if ($x[0] -isnot [array] -and $x -is [array] -and $x.Length -eq $k.Length){  
            $x |   
                ForEach-Object -Begin {$i = 0} -Process{  
                                                [array]$h.($k[$i]) += [string]$_  
                                                $i++}  -End {}  
        }  
        # manage a list of arrays passed as $servers parameter  
        elseif ($x[0] -is [array]) {  
            For ($i=0; $i -le $x.Length; $i++){         # iterate over array  
                For( $j=0;$j -lt $x[$i].length; $j++){  
                    if ($x[$i].Length -eq $k.Length){  
                        [array]$h.($k[$j]) += [string]$x[$i][$j]  
                    }  
                    else{  
                        # this exception message may be confusing because it uses zero-based references to the bad array.  
                        # if you want to use people-friendly numbering then change "ordinal$i" to "ordinal $i+1"  
                        $err = "The {0} array has an incorrect number of items (should be {1} but is {2})" -f (ordinal $i), $k.Length, $x[$i].Length  
                        Throw $err  
                    }  
                }  
            }  
        }  
        # the $servers parameter is not a single array, or a list of arrays.  
        else{  
            $err = "The `$servers parameter is not a single array of {0} items, or is not a list of arrays" -f $k.length  
            Throw $err  
        }  
        $h  # put the hash into the pipe  
    }  
      
    $hash1 = stuff '72','Server1\DEV2','1.0',2      # one array  
    $hash1  
    "-------------------"  
    $hash2 = stuff @('72','Server1\DEV2','1.0',2),@('88','Server8\DEV7','2.0',3)  # two arrays  
    $hash2  
    Try{stuff 1}Catch{$_}                                                                       # throw exception -- not an array  
    Try{stuff 1,2,3}Catch{$_}                                                                   # throw exception -- not enough items in the single array  
    Try{stuff 1,2,3,4,5}Catch{$_}                                                               # throw exception -- too may items in the single array  
    Try{stuff @('72','Server1\DEV2','1.0'),@('88','Server8\DEV7','2.0',3)}Catch{$_}             # throw exception -- too few items in an array  
    Try{stuff @('72','Server1\DEV2','1.0',2),@('88','Server8\DEV7','2.0',3, 'x')}Catch{$_}      # throw exception -- too many items in an array  
    
    
      
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.