How do multidimensional-arrays work in PowerShell? - arrays

I'm currently trying to create a program to train a foreign language.
For that i have two single dimensional array. In the first one i store all the foreign syllables or words and in the second one i'm storing the answers in my native language:
$QuestionArray = New-Object System.Collections.ArrayList
$QuestionArray = "Word1","Word2","Word3"
$AnswerArray = New-Object System.Collections.ArrayList
$AnswerArray = "Answer1","Answer2","Answer3"
Afterwards i check if the entered value is inside the answer-array. If it is i select the index and the index of the randomly selected word from the question-array. If both indexes match then the question has been answered correct otherwise its wrong.
$RandomQuestion = $QuestionArray | Get-Random
$Answer = $InputTextbox.Text
$IndexPositionQuestion = [array]::indexof($QuestionArray, $RandomQuestion)
$IndexPositionAnswer = [array]::indexof($AnswerArray, $Answer)
If($IndexPositionAnswer -eq $IndexPositionQuestion){
$RightTextbox.Text = $script:countercorrect++
}else{
$WrongTextbox.Text = $script:counterwrong++
}
The program works as intended but when i showed it to a colleague today he just told me that the comparing part of the program is coded ugly and is anything but best practice.
How could i go about it in any other way? I read a little bit about multidimensional-arrays but i just can't wrap my head around it. How would i benefit from multidimensional-arrays? How could i select the value i need from it to display, compare, check and so on?

Before digging into multidimensional arrays, why dont you try something like an array with hashtables?
$array = #(
#{
Question = 'blah?'
Answer = 'blub'
},
#{
Question = 'james'
Answer = 'Dean'
}
)
You can reference the values by
for($i = 0; $i -lt $array.Count; $i++){
$array[$i].Question
$array[$i].Answer
}
in youre example try something like
$RandomQuestionNr = 0..($QuestionArray.count -1) | Get-Random
$Answer = $InputTextbox.Text
if($array[$RandomQuestionNr].answer -eq $Answer){
$RightTextbox.Text = $script:countercorrect++
}else{
$WrongTextbox.Text = $script:counterwrong++
}

Related

String comparison in PowerShell doesn't seem to work

I am trying to compare strings from one array to every string from the other. The current method works, as I have tested it with simpler variables and it retrieves them correctly. However, the strings that I need it to compare don't seem to be affected by the comparison.
The method is the following:
$counter = 0
for ($num1 = 0; $num1 -le $serverid_oc_arr.Length; $num1++) {
for ($num2 = 0; $num2 -le $moss_serverid_arr.Length; $num2++) {
if ($serverid_oc_arr[$num1] -eq $moss_serverid_arr[$num2]) {
break
}
else {
$counter += 1
}
if ($counter -eq $moss_serverid_arr.Length) {
$unmatching_serverids += $serverid_oc_arr[$num1]
$counter = 0
break
}
}
}
For each string in the first array it is iterating between all strings in the second and comparing them. If it locates equality, it breaks and goes to the next string in the first array. If it doesn't, for each inequality it is adding to the counter and whenever the counter hits the length of the second array (meaning no equality has been located in the second array) it adds the corresponding string to a third array that is supposed to retrieve all strings that don't match to anything in the second array in the end. Then the counter is set to 0 again and it breaks so that it can go to the next string from the first array.
This is all okay in theory and also works in practice with simpler strings, however the strings that I need it to work with are server IDs and look like this:
289101b4-3e6c-4495-9c67-f317589ba92c
Hence, the script seems to completely ignore the comparison and just puts all the strings from the first array into the third one and retrieves them at the end (sometimes also some random strings from both first and second array).
Another method I tried with similar results was:
$unmatching_serverids = $serverid_oc_arr | Where {$moss_serverid_arr -NotContains $_}
Can anyone spot any mistake that I may be making anywhere?
The issue with your code is mainly the use of -le instead of -lt, collection index starts at 0 and the collection's Length or Count property starts at 1. This was causing $null values being added to your result collection.
In addition to the above, $counter was never getting reset if the following condition was not met:
if ($counter -eq $moss_serverid_arr.Length) { ... }
You would need to a add a $counter = 0 outside inner for loop to prevent this.
Below you can find the same, a little bit improved, algorithm in addition to a test case that proves it's working.
$ran = [random]::new()
$ref = [System.Collections.Generic.HashSet[int]]::new()
# generate a 100 GUID collection
$arr1 = 1..100 | ForEach-Object { [guid]::NewGuid() }
# pick 90 unique GUIDs from arr1
$arr2 = 1..90 | ForEach-Object {
do {
$i = $ran.Next($arr1.Count)
} until($ref.Add($i))
$arr1[$i]
}
$result = foreach($i in $arr1) {
$found = foreach($z in $arr2) {
if ($i -eq $z) {
$true; break
}
}
if(-not $found) { $i }
}
$result.Count -eq 10 # => Must be `$true`
As side, the above could be reduced to this using the .ExceptWith(..) method from HashSet<T> Class:
$hash = [System.Collections.Generic.HashSet[guid]]::new([guid[]]$arr1)
$hash.ExceptWith([guid[]]$arr2)
$hash.Count -eq 10 # => `$true`
The working answer that I found for this is the below:
$unmatching_serverids = #()
foreach ($item in $serverid_oc_arr)
{
if ($moss_serverid_arr -NotContains $item)
{
$unmatching_serverids += $item
}
}
No obvious differences can be seen between it and the other methods (especially for the for-loop, this is just a simplified variant), but somehow this works correctly.

Adding values from two arrays into a PSCustomObject

I am trying to combine values from two arrays into a table using a PSCustomObject.
One array has the passwords and one array has the users.
When I try to combine them in to a PSCustomObject array it only list the last value in the array, not a list.
I have tried a few different versions:
for ($i = 0; $i -lt $users.Length; $i++) {$myObject = [PSCustomObject] #{name = $users.name[$i]; User = $users.samaccountname[$i]; Mail = $users.mail[$i]; Password = $passwords[$i]}}
and
foreach ($psw in $passwords) {$users | % {$myObject = [PSCustomObject] #{name = $PSItem.name; User = $PSItem.samaccountname; Mail = $PSItem.mail; Password = $psw}}}
When I try to use += on $myobject it gives the error:
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'.
Any ideas what I am doing wrong?
Instead of using +=, simply assign all the output from the loop to a variable (also note that you'll probably want to index into $users rather than the synthetic collection(s) you get from $users.name etc. - otherwise it'll break if any property contains multiple values):
$myObjects =
for ($i = 0; $i -lt $users.Length; $i++) {
[PSCustomObject] #{
Name = $users[$i].name
User = $users[$i].samaccountname
Mail = $users[$i].mail
Password = $passwords[$i]
}
}
The error you get when using += on $myobject is caused because $myobject is of a custom type (without the 'op_Addition' method implemented).
You can use an object with this method implemented, for example ArrayList, like that:
$myObject = New-Object -TypeName "System.Collections.ArrayList"
for ($i = 0; $i -lt $users.Length; $i++) {
$myObject += [PSCustomObject] #{
Name = $users[$i].name
User = $users[$i].samaccountname
Mail = $users[$i].mail
Password = $passwords[$i]
}
}

Powershell Array not working in WinPE but OK on Windows 7

Hi Firstly I have WinPE 4 and Powershell version 4 running other powershell scripts fine in my environment.
The issue is with displaying Array information.. On windows 7 running PS Version 4 this works great...
I have a Function rounding the size off which we dont need in this example.
$a = #()
$diskdrive = gwmi win32_diskdrive
foreach($drive in $diskdrive){
$InterFaceType = "$($Drive.InterfaceType)"
If(!($InterFaceType -eq "USB")){
$Size = "$($drive.size)"
$DriveModel = "`nDrive: $($drive.deviceid.substring(4)) Model: $($drive.model)"
$Size = Get-OptimalSize $Size
$Result = $DriveModel + " " + $Size
$a += $Result
}
}
$a = $a | Sort-Object
$Drive1 = $a[0]
$Drive2 = $a[1]
$Drive3 = $a[2]
$Drive4 = $a[3]
$Drive5 = $a[4]
$Drive6 = $a[5]
$Drive1 = $Drive1.trim()
$Drive2 = $Drive2.trim()
$Drive3 = $Drive3.trim()
$Drive4 = $Drive4.trim()
$Drive5 = $Drive5.trim()
$Drive6 = $Drive6.trim()
In Windows 7 $Drive1,2,3,4,5,6 will display the following info about the Drive detected.
Drive: PHYSICALDRIVE0 Model: ST3500418AS 465.76 GB
In WinPE $Drive1,2,3,4,5,6 Are Blank...
However $Result ( which i add to my array ) is not. As soon as i take that $Result and add to the $a array in WinPE its like it clears it?
I need to loop through the for each and add each pass to an array and this is how you do it, but as WinPE is acting differently I am looking at creating a brand new dynamic variable with each pass.
For eg.
Take first pass call $Result1, then $Result2 and $result3 etc.
Maybe if I use an actual Variable ( like the $Result which appears to display in WinPE) i can work round the odd behaviour of WinPE??
Any help appreciated.
Just to add the behaviour is reminiscent of the string not being trimmed as when compiling the code on Windows 7 it was doing the same until i added the trim to the $drive variables. However i have already trimmed the variables and in WinPE it still will not show when called. Do i have to trim further? just an idea
for eg.... i was doing this.
If($a[0]){
$Radio1.BackColor = [System.Drawing.Color]::’Transparent’
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 500
$System_Drawing_Size.Height = 15
$Radio1.Size = $System_Drawing_Size
$Radio1.TabIndex = 5
$Radio1.Text = $Drive1
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 27
$System_Drawing_Point.Y = 260
$Radio1.Location = $System_Drawing_Point
$Radio1.DataBindings.DefaultDataSourceUpdateMode = 0
$Radio1.Name = "Radio1"
$form1.Controls.Add($Radio1)
}
Using $a and not an array this is what happens.
If i do this
$msg = [System.Windows.Forms.MessageBox]
$msg::Show($a[0])
I get nothing in winpe.
If i do this...
$msg = [System.Windows.Forms.MessageBox]
$msg::Show($a)
it outputs the information, however i cannot use just $a as that is only the last pass in the loop..
All this is fine in windows 7..
How can i create a New variable on each pass without an Array?
So $Result1, $Result2 etc then i can call the actual variable for the info not the array index? that might work?
Adding my suggestion as an Answer so the question can be resolved.
Try changing $a += $Results to [Array]$a = $a + $Results and see if that behaves better. It sounds like it isn't setting up $a as an array so it isn't iterating new records, it's just resetting it.

PowerShell array unshift

After reading this helpful article on the Windows PowerShell Blog, I realized I could "shift" off the first part of an array, but didn't know how to "unshift" or push elements onto the front of an array in PowerShell.
I am creating an array of hash objects, with the last read item pushed onto the array first. I'm wondering if there is a better way to accomplish this.
## Create a list of files for this collection, pushing item on top of all other items
if ($csvfiles[$collname]) {
$csvfiles[$collname] = #{ repdate = $date; fileobj = $csv }, $csvfiles[$collname] | %{$_}
}
else {
$csvfiles[$collname] = #{ repdate = $date; fileobj = $csv }
}
A couple of things to note:
I need to unroll the previous array element with a foreach loop %{$_}, because merely referencing the array would create a nested array structure. I have to have all the elements at the same level.
I need to differentiate between an empty array and one that contains elements. If I attempt to unroll an empty array, it places a $null element at the end of the array.
Thoughts?
PS: The reason the empty hash element produces a NULL value is that $null is treated as a scalar in PowerShell. For details, see https://connect.microsoft.com/PowerShell/feedback/details/281908/foreach-should-not-execute-the-loop-body-for-a-scalar-value-of-null.
ANSWER:
Looks like the best solution is to pre-create the empty array when necessary, rather than code around the $null issue. Here's the rewrite using a .NET ArrayList and a native PoSh array.
if (!$csvfiles.ContainsKey($collname)) {
$csvfiles[$collname] = [System.Collections.ArrayList]#()
}
$csvfiles[$collname].insert(0, #{ repdate = $repdate; fileobj = $csv })
## NATIVE POSH SOLUTION...
if (!$csvfiles.ContainsKey($collname)) {
$csvfiles[$collname] = #()
}
$csvfiles[$collname] = #{ repdate = $repdate; fileobj = $csv }, `
$csvfiles[$collname] | %{$_}
You might want to use ArrayList objects instead, as they support insertions at arbitrary locations. For example:
# C:\> $a = [System.Collections.ArrayList]#(1,2,3,4)
# C:\> $a.insert(0,6)
# C:\> $a
6
1
2
3
4
You can simply use a plus operator:
$array = #('bar', 'baz')
$array = #('foo') + $array
Note: this re-creates actually creates a new array instead of changing the existing one (but the $head, $tail = $array way of shifting you refer to works extactly in the same way).

create a new variable for each loop in a foreach-loop

How can I put $org into an array together with $count?
Like this example array:
$myArray = #{
1="SampleOrg";
2="AnotherSampleOrg"
}
Another example:
$myArray = #{
$count="$org";
$count="$org"
}
Example foreach:
$count=0;get-organization | foreach {$count++; $org = $_.Name.ToString();write-host $count -nonewline;write-host " $org"}
$answer = read-host "Select 1-$count"
The above will display:
1 SampleOrg
2 AnotherSampleOrg
Select 1-2:
What I would like to do afterwards is to put the array to use in a switch.
Example:
switch ($answer)
{
1 {$org=myArray[1]} #<-- or whatever that corresponds to "SampleOrg"
2 {$org=myArray[2]} #<-- or whatever that corresponds to "AnotherSampleOrg"
}
You have to initialize your hashtable somewhere before the loop:
$myArray = #{}
and add a
$myArray.Add($count, $org)
to your foreach-loop.
EDIT: For the discussion about hastable/array see the whole thread ;) I just kept the name of the variable from the original posting
Looks like you're confusing arrays and Hashtables. Arrays are ordered, and indexed by an numeric value. Hashtables are associative, and indexed by any value that has equality defined.
This is array syntax
$arr = #(1,2,3)
and this is Hashtable syntax
$ht = #{red=1;blue=2;}
For your question, the following will work
$orgs = #(get-organization | % { $_.Name })
this will create a 0 based array, mapping int -> OrgName, so
$orgs[$answer]
will get the correct name. Or if you're using 1 based indexing
$orgs[$answer-1]
Note, I removed the switch, as there's no reason for it.

Resources