Handle 4 For loops at once? - powershell

Introduction to question
I'm trying to search for the following values: '02-08-1997' and '01-08-1997' in string $b1. To find that value, I used searching for the starting and ending index of the values around it.
How did I do it?
To do this I used 4 For loops, only to execute the exact same thing over and over again. I know this can be done much simpler; so far I haven't found a way to do so yet. I have to write multiple scripts like this, so I really need to find a simpler - more easy to read - way to do so.
These are 2 of the For loops I'm using:
$b1 = 'set-aduser -identity 3423-234-23-42-432 dorstm -replace #{ geboortedatum = 01-08-1997 } waarde_org = 02-08-1997'
For ($i = 0; $i -lt $b1InArray.Length; $i++) {
if ($b1InArray[$i].contains("=")) {
$e = $i
break
}
}
For ($j = ($e + 1); $j -lt $b1InArray.Length; $j++) {
if ($b1InArray[$j].contains("}")) {
$f = $j
break
}
}

Looks like you're trying to use more of a Java solution. In Powershell you can use 'ForEach'
$b1 = 'set-aduser -identity 3423-234-23-42-432 dorstm -replace #{ geboortedatum = 01-08-1997 } waarde_org = 02-08-1997'
foreach ($b in $b1) {
if ( $b -contains '=') {
$f = $j
}
}
Note: $b in the foreach loop condition can be anything you want it to be just like $i in Java.

If you want to get the index of the strings you are searching for: there's a method for that:
$b1.IndexOf('02-08-1997')
$b1.IndexOf('01-08-1997')
No need to write loops. For simple things like this .Net has a working method for you.

Many ways to do this. This could work for you:
(Select-String -InputObject $b1 -allmatches -Pattern '\d{2}-\d{2}-\d{4}').matches.value
it will output all dates from your $b1 string as an array of strings. Assuming all dates have the same format like your example.
Fiddle with regex at https://regex101.com/

Related

Find index of array where condition is true in PowerShell [duplicate]

I have trouble of getting index of the current element for multiple elements that are exactly the same object:
$b = "A","D","B","D","C","E","D","F"
$b | ? { $_ -contains "D" }
Alternative version:
$b = "A","D","B","D","C","E","D","F"
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" })
This will return:
D
D
D
But this code:
$b | % { $b.IndexOf("D") }
Alternative version:
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" }) | % { $b.IndexOf($_) }
Returns:
1
1
1
so it's pointing at the index of the first element. How to get indexes of the other elements?
You can do this:
$b = "A","D","B","D","C","E","D","F"
(0..($b.Count-1)) | where {$b[$_] -eq 'D'}
1
3
6
mjolinor's answer is conceptually elegant, but slow with large arrays, presumably due to having to build a parallel array of indices first (which is also memory-inefficient).
It is conceptually similar to the following LINQ-based solution (PSv3+), which is more memory-efficient and about twice as fast, but still slow:
$arr = 'A','D','B','D','C','E','D','F'
[Linq.Enumerable]::Where(
[Linq.Enumerable]::Range(0, $arr.Length),
[Func[int, bool]] { param($i) $arr[$i] -eq 'D' }
)
While any PowerShell looping solution is ultimately slow compared to a compiled language, the following alternative, while more verbose, is still much faster with large arrays:
PS C:\> & { param($arr, $val)
$i = 0
foreach ($el in $arr) { if ($el -eq $val) { $i } ++$i }
} ('A','D','B','D','C','E','D','F') 'D'
1
3
6
Note:
Perhaps surprisingly, this solution is even faster than Matt's solution, which calls [array]::IndexOf() in a loop instead of enumerating all elements.
Use of a script block (invoked with call operator & and arguments), while not strictly necessary, is used to prevent polluting the enclosing scope with helper variable $i.
The foreach statement is faster than the Foreach-Object cmdlet (whose built-in aliases are % and, confusingly, also foreach).
Simply (implicitly) outputting $i for each match makes PowerShell collect multiple results in an array.
If only one index is found, you'll get a scalar [int] instance instead; wrap the whole command in #(...) to ensure that you always get an array.
While $i by itself outputs the value of $i, ++$i by design does NOT (though you could use (++$i) to achieve that, if needed).
Unlike Array.IndexOf(), PowerShell's -eq operator is case-insensitive by default; for case-sensitivity, use -ceq instead.
It's easy to turn the above into a (simple) function (note that the parameters are purposely untyped, for flexibility):
function get-IndicesOf($Array, $Value) {
$i = 0
foreach ($el in $Array) {
if ($el -eq $Value) { $i }
++$i
}
}
# Sample call
PS C:\> get-IndicesOf ('A','D','B','D','C','E','D','F') 'D'
1
3
6
You would still need to loop with the static methods from [array] but if you are still curious something like this would work.
$b = "A","D","B","D","C","E","D","F"
$results = #()
$singleIndex = -1
Do{
$singleIndex = [array]::IndexOf($b,"D",$singleIndex + 1)
If($singleIndex -ge 0){$results += $singleIndex}
}While($singleIndex -ge 0)
$results
1
3
6
Loop until a match is not found. Assume the match at first by assigning the $singleIndex to -1 ( Which is what a non match would return). When a match is found add the index to a results array.

Powershell - output a foreach loop to one line

I am trying to take the output of my foreach loop and apply the array to a string that reads on one line. Here is my code so far:
$upper = 65..90
$lower = 97..122
foreach ($i in $upper)
{
[char]$i
}
foreach ($i in $lower)
{
[char]$i
}
I'm guessing I need to convert the output of the scriptblock to a variable and use the -join option, but everywhere I look I'm struggling to find how to structure that. Any guidance would be appreciated.
For this particular case, ForEach(type convertToType) is very useful, here is a cool way to get your lower and upper case dictionary string:
$lowerDict = [string]::new(([int][char]'a'..[int][char]'z').ForEach([char]))
$upperDict = $lowerDict.ToUpper()
If you have access to PowerShell Core, it can be reduced to:
$lowerDict = [string]::new('a'..'z')
$upperDict = $lowerDict.ToUpper()
As for what you are struggling on, how to do it with what you currently have (a foreach loop). You can capture all the output from the loop first:
$upper = foreach ($i in 65..90) { [char]$i }
Now, $upper is an array of chars, then to convert it to string, you can either use -join (guessed right) or [string]::new(...) as I did on my previous example:
$upperDict = -join $upper
# OR
$upperDict = [string]::new($upper)

Fastest way to match two large arrays of objects by key in Powershell

I have two powershell arrays of objects generated via Import-CSV, and I must match them by one of their properties. Specifically, it is a 1:n relationship so currently I'm following this pattern:
foreach ($line in $array1) {
$match=$array2 | where {$_.key -eq $line.key} # could be 1 or n results
...# process here the 1 to n lines
}
, which is not very efficient (both tables have many columns) and takes a time that is unacceptable for our needs. Is there a fastest way to perform this match?
Both data sources come from a csv file, so using something instead of Import-CSV would be also welcome.
Thanks
The standard method is to index the data using a hashtable (or dictionary/map in other languages).
function buildIndex($csv, [string]$keyName) {
$index = #{}
foreach ($row in $csv) {
$key = $row.($keyName)
$data = $index[$key]
if ($data -is [Collections.ArrayList]) {
$data.add($row) >$null
} elseif ($data) {
$index[$key] = [Collections.ArrayList]#($data, $row)
} else {
$index[$key] = $row
}
}
$index
}
$csv1 = Import-Csv 'r:\1.csv'
$csv2 = Import-Csv 'r:\2.csv'
$index2 = buildIndex $csv2, 'key'
foreach ($row in $csv1) {
$matchedInCsv2 = $index2[$row.key]
foreach ($row2 in $matchedInCsv2) {
# ........
}
}
Also, if you need speed and iterate a big collection, avoid | pipelining as it's many times slower than foreach/while/do statements. And don't use anything with a ScriptBlock like where {$_.key -eq $line.key} in your code because execution context creation adds a ridiculously big overhead compared to the simple code inside.

How to declare and get array value

I am new to Powershell, first time using it.
I have declared an array and use array value but using below code, I am not able to retrieve the array value...Any idea what I am missing here?
Just FYI.. I am executing script in ADOBE ILLUSTRATOR and for testing I am using 3 here in condition (for loop)... will use $array later
$array = "a.jpg","b.jpg","c.jpg";
for ($i=1; $i-le=3; $i++)
{
$.writeln("This is line number " + $array[$i]);
var targetFileName = $array[$i]+'.png';
$.writeln(targetFileName);
}
I tried $array[$i].toString() as well but still not getting values... I am getting 0
Any help is appreciated and thanks in advance to all for your help
for ($i=1; $i-le=3; $i++)
The condition in the above line doesn't have a valid comparison operator. Change that to
for ($i=1; $i -le 3; $i++)
if you want the loop to terminate after 3 cycles.
$.writeln("This is line number " + $array[$i]);
var targetFileName = $array[$i]+'.png';
$.writeln(targetFileName);
This is not valid PowerShell. Looks more like JavaScript to me. In PowerShell it should probably look like this:
Write-Output "This is line number $i"
$targetFileName = $array[$i] + '.png'
Write-Output $targetFileName
or shorter
"This is line number $i"
$array[$i] + '.png'
Note that PowerShell arrays are zero-based, so the last iteration ($array[3]) will return $null instead of an element from the array. If you want to iterate over the elements of the array you should change your loop to this:
for ($i=0; $i -lt $array.Length; $i++) {
"This is line number $($i+1)"
$array[$i] + '.png'
}
or (better) pipe your array into a foreach loop:
$i = 0
$array | % {
"This is line number " + ++$i
$_ + '.png'
}

Powershell - Remove space between two variables

I have two array's which contain a selection of strings with information taken from a text file. I then use a For Loop to loop through both arrays and print out the strings together, which happen to create a folder destination and file name.
Get-Content .\PostBackupCheck-TextFile.txt | ForEach-Object { $a = $_ -split ' ' ; $locationArray += "$($a[0]):\$($a[1])\" ; $imageArray += "$($a[2])_$($a[3])_VOL_b00$($a[4])_i000.spi" }
The above takes a text file, splits it into separate parts, stores some into the locationArray and other information in the imageArray, like this:
locationArray[0] would be L:\Place\
imageArray[0] would be SERVERNAME_C_VOL_b001_i005.spi
Then I run a For Loop:
for ($i=0; $i -le $imageArray.Length - 1; $i++)
{Write-Host $locationArray[$i]$imageArray[$i]}
But it places a space between the L:\Place\ and the SERVERNAME_C_VOL_b001_i005.spi
So it becomes: L:\Place\ SERVERNAME_C_VOL_b001_i005.spi
Instead, it should be: L:\Place\SERVERNAME_C_VOL_b001_i005.spi
How can I fix it?
Option #1 - for best readability:
{Write-Host ("{0}{1}" -f $locationArray[$i], $imageArray[$i]) }
Option #2 - slightly confusing, less readable:
{Write-Host "$($locationArray[$i])$($imageArray[$i])" }
Option #3 - more readable than #2, but more lines:
{
$location = $locationArray[$i];
$image = $imageArray[$i];
Write-Host "$location$image";
}