Powershell for loop throwing "the array index evaluated to null" - arrays

For starters, I'm on Fedora 30 using PSCore version 6.2.1. I've encountered this issue in GNOME Terminal and the vscode snap.
I'm on the first challenge of the PSKoans module and I'm stuck when trying to use a for loop. I am given an array of strings, each of which is a collection of strings separated by commas.
$StockData = #(
"Date,Open,High,Low,Close,Volume,Adj Close"
"2012-03-30,32.40,32.41,32.04,32.26,31749400,32.26"
"2012-03-29,32.06,32.19,31.81,32.12,37038500,32.12"
) # The array is much longer than that, but shortened for simplicity's sake
So, my idea is to build a hashtable out of each subsequent string line in the array by using the first string in the array as keys and each following line as a set of values. I'm using -split to split the values apart from within the strings. I want to use a for loop to iterate through the array and pull values, building a hastable in a file to be read later like so:
# Build the array of keys
[array]$keys = $StockData[0] -split ','
# Begin for loop, using $i as int
for ($i = 1, $StockData[$i], $i++) {
# Create a text file for each hastable
New-Item -Name "ht$i.txt" -ItemType File
# Split current string into values
$values = $StockData[$i] -split ','
# Set value int
$valuesInt = 0
foreach ($key in $keys) {
Add-Content -Path "./ht$i.txt" -Value "$key = $values[$valuesInt]"
$valuesInt++
}
}
As I run that, I get the following error:
Index operation failed; the array index evaluated to null.
At /home/user/PSKoans/Foundations/SolutionStockChallenge.ps1:28 char:6
+ for ($i = 1, $stockData[$i], $i++) {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArrayIndex
I've looked it up and I find all kinds of scenarios in which people get this error message. I didn't really find a solid explanation for the error message the might lead me to an answer.
Reading the error message, it doesn't make sense to me. the array index evaluated to null...but the array index in the first case is $StockData[1] which is a valid index and should return $true and continue with the loop. Am I missing something?

The syntax of your for loop is wrong. The for loop uses semi-colons as separators.
for ($i = 1, $StockData[$i], $i++) {
should be
for ($i = 1; $StockData[$i]; $i++) {

ConvertFrom-Json in PowerShell Core has the coolest switch - AsHashTable. Try this:
$StockData | convertfrom-csv | convertto-json | ConvertFrom-Json -AsHashtable

Related

Powershell Get-Content + Array

how's it going?
I'm new on Powershell and I'm trying to simplify my code in order that I need to perform the same action in two files, the only thing that changes is the File Name and ReadCount size (15000 for the first file and 50000 for the second one).
When I run it the error shows:
Get-Content : An object at the specified path
C:\Folder\08_configuration_items 11_CI-Contract-new[0].csv does not
exist, or has been filtered by the -Include or -Exclude parameter. At
line:2 char:7
+ $i=0; Get-Content "C:\Folder\$fileArray[$len].csv" -ReadCount $sizeA ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Get-Content : An object at the specified path
C:\Folder\08_configuration_items 11_CI-Contract-new[0]_1.csv does not
exist, or has been filtered by the -Include or -Exclude parameter. At
line:3 char:20
+ ... bookContent = Get-Content "C:\Folder\$fileArray[$len]_1.csv" | Selec ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
That's the code, not sure if I'm accessing the Array using the right way on Powershell.
$sizeArray = #(15000,50000)
$fileArray = #("08_configuration_items", "11_CI-Contract-new")
for($len=0; $len -le 1; $len++) {
$i=0; Get-Content "C:\Folder\$fileArray[$len].csv" -ReadCount $sizeArray[$len] | %{$i++; $_ | Out-File "C:\Folder\$fileArray[$len]_$i.csv" -Encoding "UTF8"}
$WorkbookContent = Get-Content "C:\Folder\$fileArray[$len]_1.csv" | Select -Index 0
for($j=2; $j -le $i; $j++) {
$CurrentFileContent = Get-Content "C:\Folder\$fileArray[$len]_$j.csv"
#($WorkbookContent, $CurrentFileContent) | Set-Content "C:\Folder\$fileArray[$len]_$j.csv"
}
}
Any ideias?
Thanks a lot
The problem here is with string interpolation. A variable name within a string will expand up until it reaches a special character in that name. Then it will append the remainder of the string and any interpolated strings afterwards. This commonly happens with the . character when accessing a property of an object within a string. A simple solution is to use the subexpression operator ($()).
Get-Content "C:\Folder\$($fileArray[$len]).csv"
An alternative is to build the path string another way and then pass it into the command. The method below uses the format operator (-f).
$Path = "C:\Folder\{0}.csv" -f $fileArray[$len]
Get-Content $Path
Your code with the subexpression operator added will look like the following:
$sizeArray = #(15000,50000)
$fileArray = #("08_configuration_items", "11_CI-Contract-new")
for($len=0; $len -le 1; $len++) {
$i=0; Get-Content "C:\Folder\$($fileArray[$len]).csv" -ReadCount $sizeArray[$len] | %{$i++; $_ | Out-File "C:\Folder\$($fileArray[$len])_$i.csv" -Encoding "UTF8"}
$WorkbookContent = Get-Content "C:\Folder\$($fileArray[$len])_1.csv" | Select -Index 0
for($j=2; $j -le $i; $j++) {
$CurrentFileContent = Get-Content "C:\Folder\$($fileArray[$len])_$j.csv"
#($WorkbookContent, $CurrentFileContent) | Set-Content "C:\Folder\$($fileArray[$len])_$j.csv"
}
}
You can see this behavior on a simpler scale using your $fileArray variable.
$filearray
08_configuration_items
11_CI-Contract-new
# Notice how the [0] gets appended to the string-cast $fileArray
"$filearray[0]"
08_configuration_items 11_CI-Contract-new[0]
$filearray[0]
08_configuration_items
"$($filearray[0])"
08_configuration_items
Since $fileArray is an array of strings, you have another unintended effect. With "$fileArray[0]", $fileArray will be interpolated and converted to a string output rather than an array. PowerShell by default will join array elements by a single space when casting as a string. So the resulting output format is arrayItem1 arrayItem2 arrayItem3[0]. [0] is not included as part of the variable evaluation.

what bad consequence could using an array have 'in this situation'?

My question is two fold I'm playing around with PS and trying new stuff, I wrote a small script that generates full names by exporting first names from a txt.file and last names from another txt.file and finally randomly combines the 2 to create a full name, this is the script:
$Firstnames = Get-Content -Path 'C:\Users\user\Desktop\Firstname.txt'
$Lastnames = Get-Content -Path 'C:\Users\user\Desktop\Lastnames.txt'
$feminine = $firstnames | ?{$_ -like "*;frau"} | %{$_.Split(';')[0]}
Write-Host "Random full name generator"
$numberofNames = 1..(Read-Host 'how many combination do you want to generate?')
$numberofNames | foreach {
$f = $feminine[ (Get-Random $feminine.count)]
$l = $Lastnames[ (Get-Random $Lastnames.count)]
$full = $f+" "+$l
Write-output $full
}
1- now $numberofNames is an array 'a range operator', I would like to know what bad consequences could use this method have? is this the best approach for the users input?
2- what is the key difference between using for example $a = 100 and $a = 1..100?
in case you need to know:
$firstnames looks something like this:
Linda;frau
Aline;frau
Lisa;frau
Katja;frau
Karen;frau
and $lastnames:
smith
anderson
müller
klein
Rex
thank you
1- now $numberofNames is an array 'a range operator', i would like to know what bad consequences could using this method have? is this the best approach for the users input?
It's valid. Even if the user adds e.g. a string as input Powershell will throw an error that it could not cast the input to an int:
1 .. "test" | % {Write-Host $_ }
Cannot convert value "test" to type "System.Int32". Error: "Input string was not in a correct format."
At line:1 char:1
+ 1 .. "test" | % {Write-Host $_ }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvalidCastFromStringToInteger
Even a neg. int shouldn't be a problem since you're not using the array content for any indexing operations.
2- what is the key difference between using for example $a = 100 and $a = 1..100?
$a = 100 points to integer with the value 100.
$a = 1..100 is an array with 100 entries.

Out-File output is missing line feeds between lines of data

I am passing in an array of $users.
PS C:\> $users | ft
ID DisplayName AdminID first last Password
---- ----------- ------- ----- ---- --------
Axyz Axyz, Bill NBX_Admin Bill Axyz Secret
The code:
$y = #()
$y = "Create Users process. Run started at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"
foreach ($x in $users) {
$y += "User $($x.DisplayName) with NNN of $($x.ID)"
}
$y += "Completed at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"
$y | Out-File "Log.txt"
$y is now an unformatted string array. When I type $y to the screen, it looks great.
If I direct it to Format-Table, it looks great (no headings).
When I output it to a file, and type that file at a Command Prompt (cmd.exe), it looks great.
However, when I pull it up in Notepad, all the output appears on a single line. To be precise, all the data is there, there are no lines of data missing, but there are no CR/LF so all of the data appears on a single line within the file when viewed with Notepad.exe.
As AdminOfThings correctly points out:
While $y = #() assigns an empty array to $y, it doesn't type-constrain that variable, so your very next assignment - $y = "Create Users process ..." - changes the variable type to a string.
Simply using += instead of = in that subsequent assignment would have prevented the problem: $y += "Create Users process ...".
Alternatively, type-constraining the variable creation - [array] $y = #() - i.e., placing a type literal to the left of the variable being assigned (akin to a cast) - would have prevented the problem too.
Subsequent use of += therefore performs simple string concatenation rather than the desired gradual building of an array, with no separators between the "lines" added.[1]
By contrast, had you used an array as intended, both Out-File and Set-Content would automatically insert platform-appropriate newlines[2] between the elements, plus one at the end, on saving (in PSv5+ you can use the -NoNewline switch to opt out).
That said, using += to "extend" an array is inefficient, because what PowerShell must do behind the scenes is create a new array containing the old elements plus the new one(s), given that arrays are fixed-size data structures.
While the performance penalty for use of += to "extend" arrays in a loop only really matters with high iteration counts, it is more concise, convenient and efficient to let PowerShell create arrays for you implicitly, by using your foreach loop as an expression:
# Initialize the array and assign the first element.
# Due to the type constraint ([array]), the RHS string implicitly becomes
# the array's 1st element.
[array] $y = "Create Users process. Run started at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"
# Add the strings output by the foreach loop to the array.
# PowerShell implicitly collects foreach output in an array when
# you use it in as an expression.
$y += foreach ($x in $users)
{
"User $($x.displayname) with NNN of $($x.ID)"
}
# Add the final string to the array.
$y += "Completed at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"
# Send the array to a file with Out-File, which separates
# the elements with newlines and adds a trailing one.
# Windows PowerShell:
# Out-File creates UTF-16LE-encoded files.
# Set-Content, which can alternatively be used, creates "ANSI"-encoded files.
# PowerShell Core:
# Both cmdlets create UTF-8-encoded files without BOM.
$y | Out-File "Log.txt"
Note that you can similarly use for, if, do / while / switch statements as expressions.
In all cases, however, as of PowerShell 7.0, these statements can only serve as expressions by themselves; regrettably, using them as the first segment of a pipeline or embedding them in larger expressions does not work - see this GitHub issue.
[1] A simple demonstration of your problem:
# The initialization of $y as #() is overridden by $y = 'first'.
PS> $y = #(); $y = 'first'; $y += 'second'; $y
firstsecond # !! $y contains a single string built with string concatenation
The description of your symptoms is therefore not consistent with your code, as you should have seen a single-line output string in all scenarios (printing directly to the screen / via Format-Table, sending to a file and type-ing that from cmd.exe).
[2] The platform-appropriate newline is reflected in [Environment]::NewLine, and it is "`r`n" (CRLF) on Windows, and just "`n" (LF) on Unix-like platforms (in PowerShell Core).
As using += recreates the array on every iteration I'd suggest to assign the output of a ForEach-Object with it's -Begin, -Process and -End sections to a variable also using a more common approach of the format operator.:
$Log = $users | ForEach-Object -Begin {
"Create Users process. Run started at [{0:MM/dd/yyyy} {0:HH:mm:ss}]" -f (Get-Date)
} -Process {
"User {0} with NNN of {1}" -f $_.DisplayName,$_.ID
} -End {
"Completed at [{0:MM/dd/yyyy} {0:HH:mm:ss}]" -f (Get-Date)
}
$Log | Set-Content "Log.txt"

Convert txt to array in powershell

I have a powershell script and a txt database with different number of elements per line.
My txt file is list.txt:
"10345","doomsday","life","hope","run","stone"
"10346","ride","latest","metal"
My powershell script search.ps1:
#Get file path
$path = Split-Path $script:MyInvocation.MyCommand.Path
$search = #()
Get-Content -LiteralPath "$path\list.txt" | ForEach-Object {
$search += $_
}
So, how to convert each line as a element of array? As this:
$search = #(("10345","doomsday","life","hope","run","stone"),("10346","ride","latest","metal"))
To operate as:
echo $search[0][0]
Here's a concise PSv4+ solution:
$search = (Get-Content -LiteralPath $path\list.txt).ForEach({ , ($_ -split ',') })
The .ForEach() method operates on each line read from the input file by Get-Content.
$_ -split ',' splits each line into an array of strings by separator ,
, (...) wraps this array in an aux. single-item array to ensure that the array is effectively output as a whole, resulting in an array of arrays as the overall output.
Note: Strictly speaking, the .ForEach() method outputs a [System.Collections.ObjectModel.Collection[psobject]] collection rather than a regular PowerShell array ([object[]]), but for all practical purposes the two types act the same.
Note: The .ForEach() method was chosen as a faster alternative to a pipeline with the ForEach-Object (%) cmdlet.
Note that the .ForEach() method requires storing the input collection in memory as a whole first.
A faster and more memory-efficient, though perhaps slightly obscure alternative is to use a switch statement with the -file option:
$search = switch -file $path\list.txt { default { , ($_ -split ',') } }
switch -file processes each line of the specified file.
Since each line should be processed, only a default branch is used, in which the desired splitting is performed.
Use -split. A code snippet you can debug in ISE or VSCode below.
$x1 = #'
"10345","doomsday","life","hope","run","stone"
"10346","ride","latest","metal"
'#
$data = $x1 -split "`r`n"
$data.Count
$data[0] -split ","
$arr = #()
foreach ($row in $data)
{
$arr += ,($row -split ",")
}
"arr"
$arr
"0,3"
$arr[0][3]
"1,3"
$arr[1][3]
So you can split each line in your file returned from Get-Content and add it to your new array which lets you reference how you wanted...
There are other ways you can use your data depending on your needs.
Assuming you do not want each item quoted, you might consider to not using the -Split operator but just evaluating each line with the Invoke-Expression cmdlet or using a more secure [ScriptBlock] for this:
$Search = Get-Content ".\list.txt" | ForEach-Object {,#(&([ScriptBlock]::Create($_)))}

Powershell Array funnction

I am running below script and retrieve information from the template and assign permission. Here I would like to get the User as array input my below script is not processing the user as array.
$userObj = [PSCustomObject]((Get-Content -Raw C:\txt\sample.txt) -replace ':','=' | ConvertFrom-StringData)
[array]$userObj.User
for ($i[0]; $userObj.user; $i++) {
Add-MailboxPermission -Identity $userObj.Identity -User $userObj.User -AccessRights FullAccess -InheritanceType All -confirm:$false
}
Here is my text input which is converted as custom object
$userObj.User is a string with comma-separated names. Casting it to an array just gives you an array with one string with comma-separated names, not an array of the names.
[array]$userObj.User ⇒ [ 'auto,auto1' ]
[array]$userObj.User ⇏ [ 'auto', 'auto1' ]
To get an array of the names from the comma-separated string you need to split it:
$userObj.User -split ','
Also, your for loop is broken. Those loops have the following structure:
for (loop variable initialization; condition; loop variable incrementation)
e.g.
for ($i=0; $i -lt 10; $i++)
But you probably don't need a for loop here anyway. If you want to run a command for each element of the array resulting from your split operation use a ForEach-Object instead:
$userObj.User -split ',' | ForEach-Object {
Add-MailboxPermission -Identity $userObj.Identity -User $_ ...
}

Resources