Powershell - print the list length respectively - arrays

I want to write two things in Powershell.
For example;
We have a one list:
$a=#('ab','bc','cd','dc')
I want to write:
1 >> ab
2 >> bc
3 >> cd
4 >> dc
I want this to be dynamic based on the length of the list.
Thanks for helping.

Use a for loop so you can keep track of the index:
for( $i = 0; $i -lt $a.Count; $i++ ){
"$($i + 1) >> $($a[$i])"
}
To explain how this works:
The for loop is defined with three sections, separated by a semi-colon ;.
The first section declares variables, in this case we define $i = 0. This will be our index reference.
The second section is the condition for the loop to continue. As long as $i is less than $a.Count, the loop will continue. We don't want to go past the length of the list or you will get undesired behavior.
The third section is what happens at the end of each iteration of the loop. In this case we want to increase our counter $i by 1 each time ($i++ is shorthand for "increment $i by 1")
There is more nuance to this notation than I've included but it has no bearing on how the loop works. You can read more here on Unary Operators.
For the loop body itself, I'll explain the string
Returning an object without assigning to a variable, such as this string, is effectively the same thing as using Write-Output.
In most cases, Write-Output is actually optional (and often is not what you want for displaying text on the screen). My answer here goes into more detail about the different Write- cmdlets, output streams, and redirection.
$() is the sub-expression operator, and is used to return expressions for use within a parent expression. In this case we return the result of $i + 1 which gets inserted into the final string.
It is unique in that it can be used directly within strings unlike the similar-but-distinct array sub-expression operator and grouping operator.
Without the subexpression operator, you would get something like 0 + 1 as it will insert the value of $i but will render the + 1 literally.
After the >> we use another sub-expression to insert the value of the $ith index of $a into the string.
While simple variable expansion would insert the .ToString() value of array $a into the final string, referencing the index of the array must be done within a sub-expression or the [] will get rendered literally.
Your solution using a foreach and doing $a.IndexOf($number) within the loop does work, but while $a.IndexOf($number) works to get the current index, .IndexOf(object) works by iterating over the array until it finds the matching object reference, then returns the index. For large arrays this will take longer and longer with each iteration. The for loop does not have this restriction.
Consider the following example with a much larger array:
# Array of numbers 1 through 65535
$a = 1..65535
# Use the for loop to output "Iteration INDEXVALUE"
# Runs in 106 ms on my system
Measure-Command { for( $i = 0; $i -lt $a.Count; $i++ ) { "Iteration $($a[$i])" } }
# Use a foreach loop to do the same but obtain the index with .IndexOf(object)
# Runs in 6720 ms on my system
Measure-Command { foreach( $i in $a ){ "Iteration $($a.IndexOf($i))" } }
Another thing to watch out for is that while you can change properties and execute methods on collection elements, you can't change the element values of a non-collection collection (any collection not in the System.Concurrent.Collections namespace) when its enumerator is in use. While invisible, foreach (and relatedly ForEach-Object) implicitly invoke the collection's .GetEnumerator() method for the loop. This won't throw an error like in other .NET languages, but IMO it should. It will appear to accept a new value for the collection but once you exit the loop the value remains unchanged.
This isn't to say the foreach loop should never be used or that you did anything "wrong", but I feel these nuances should be made known before you do find yourself in a situation where a better construct would be appropriate.

Okey,
I fixed that;
$a=#('ab','bc','cd','dc')
$a.Length
foreach ($number in $a) {
$numberofIIS = $a.IndexOf($number)
Write-Host ($numberofIIS,">>>",$number)
}

Bender's answer is great, but I personally avoid for loops if at all possible. They usually require some awkward indexing into arrays and that ugly setup... The whole thing just ends up looking like hieroglyphics.
With a foreach loop it's our job to keep track of the index (which is where this answer differs from yours) but I think in the end it is more readable then a for loop.
$a = #('ab', 'bc', 'cd', 'dc')
# Pipe the items of our array to ForEach-Object
# We use the -Begin block to initialize our index variable ($x)
$a | ForEach-Object -Begin { $x = 1 } -Process {
# Output the expression
"$x" + ' >> ' + $_
# Increment $x for next loop
$x++
}
# -----------------------------------------------------------
# You can also do this with a foreach statement
# We just have to intialize our index variable
# beforehand
$x = 1
foreach ($number in $a){
# Output the expression
"$x >> $number"
# Increment $x for next loop
$x++
}

Related

Perl: grep from multiple arrays at once

I have multiple arrays (~32). I want to remove all blank elements from them. How can it be done in a short way (may be via one foreach loop or 1-2 command lines)?
I tried the below, but it's not working:
my #refreshArrayList=("#list1", "#list2", "#list3","#list4", "#list5", "#list6" , "#list7");
foreach my $i (#refreshArrayList) {
$i = grep (!/^\s*$/, $i);
}
Let's say, #list1 = ("abc","def","","ghi"); #list2 = ("qwe","","rty","uy", "iop"), and similarly for other arrays. Now, I want to remove all blank elements from all the arrays.
Desired Output shall be: #list1 = ("abc","def","ghi"); #list2 = ("qwe","rty","uy", "iop") ### All blank elements are removed from all the arrays.
How can it be done?
You can create a list of list references and then iterator over these, like
for my $list (\#list1, \#list2, \#list3) {
#$list = grep (!/^\s*$/, #$list);
}
Of course, you could create this list of list references also dynamically, i.e.
my #list_of_lists;
push #list_of_lists, \#list1;
push #list_of_lists, \#list2;
...
for my $list (#list_of_lists) {
#$list = grep (!/^\s*$/, #$list);
}
#$_ = grep /\S/, #$_ for #AoA; # #AoA = (\#ary1, \#ary2, ...)
Explanation
First, this uses the statement modifier, "inverting" the usual for loop syntax into the form STMT for LIST
The for(each) modifier is an iterator: it executes the statement once for each item in the LIST (with $_ aliased to each item in turn).
It is mostly equivalent to a "normal" for loop, with the notable difference being that no scope is set and so there is no need to tear it down either, adding a small measure of efficiency.† Ae can have only one statement; but then again, that can be a do block. Having no scope means that we cannot declare lexical variables for the statement (unless a do block is used).
So the statement is #$_ = grep /\S/, #$_, executed for each element of the list.
In a for(each) loop, the variable that is set to each element in turn as the list is iterated over ("topicalizer") is an alias to those elements. So changing it changes elements. From perlsyn
If VAR is omitted, $_ is set to each value.
If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop.
In our case $_ is always an array reference, and then the underlying array is rewritten by dereferencing it (#$_) and assigning to that the output list of grep, which consists only of elements that have at least one non-space character (/\S/).
† I ran a three-way benchmark, of the statement-modifier loop against a "normal" loop with and without a topical variable.
For adding 100e6 numbers I get 8-11% speedup (on both a desktop and a server) and with a more involved calculation ($r = ($r + $_ ) / sqrt($_)) it's 4-5%.
A side observation: In both cases the full for loop without a variable (using default $_ for the topicalizer) is 1-2% faster than the one with a lexical topical variable set.

Powershell: Piping output of pracl command to array

pracl is a sysinternal command that can be used to list the ACLs of a directory. I have a list of shares and I want to create a csv file such that for each ACL entry, I want the share path in one column and share permission in the next. I was trying to do that by using the following code
$inputfile = "share.txt"
$outputFile = "out.csv"
foreach( $path in Get-Content $inputfile)
{
$results=.\pracl.exe $path
{
foreach ($result in $results) {write-host $path,$line}
}
$objResult = [pscustomobject]#{
Path = $Path
Permission = $line
}
$outputArray += $objResult
$objresult
}
$outputArray | Export-Csv -Path $outputfile -NoTypeInformation
It failed with the following error :-
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'.
At C:\Users\re07393\1\sample.ps1:14 char:1
+ $outputArray += $objResult
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (op_Addition:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Any suggestions ?
You're trying to create an array of [pscustomobject]s in your $outputArray variable iteratively, using +=, but you're not initializing $outputArray as an array - see the bottom section for an explanation of the resulting behavior.
Thus, the immediate solution to your problem is to do just that:
# Do this before your `foreach` loop, then `+=` will work for appending elements.
$outputArray = #()
However, using += to add to arrays is inefficient, because in reality a new array instance must be created every time, because arrays are immutable data structures. That is, every time += is used, PowerShell creates a new array instance behind the scenes to which the existing elements as well as the new element are copied.
A simpler and much more efficient approach is to let PowerShell create an array for you, by using the foreach loop as an expression and assigning it to a variable as a whole:
That is, whatever is output in every iteration of the loop is automatically collected by PowerShell:
A simplified example:
# Create an array of 10 custom objects
[array] $outputArray = foreach ($i in 1..10) {
# Create and implicitly output a custom object in each iteration.
[pscustomobject] #{
Number = $i
}
}
Note the use of type constraint [array] to the left of $outputArray, which ensures that the variable value is always an array, even if the loop happens to produce just one output object (in which case PowerShell would otherwise just store that object itself, and not wrap it in an array).
Note that you can similarly use for, if, do / while / switch statements as expressions.
In all cases, however, 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 GitHub issue #6817.
As for what you tried:
$outputArray += $objResult
Since you didn't initialize $outputArray before the loop, the variable is implicitly created in the loop's first iteration:
If the LHS variable doesn't exist yet, += is effectively the same as =: that is, the RHS is stored as-is in the LHS variable, so that $outputArray now contains a [pscustomobject] instance.
In the second iteration, because $outputArray now has a value, += now tries to perform a type-appropriate + operation (such as numeric addition for numbers, and concatenation for strings), but no + (op_Addition()) operation is defined for type [pscustomobject], so the operation fails with the error message you saw.

how can i make foreach output two different files?

I have this variable
$sync_output = Join-Path $syncPATH $CUBE_input
I have this foreach loop:
$i=0
$Destination_Server = #()
foreach($row in $Table | where { $_.cube_name -match $CUBE })
{
$i++
$Destination_Server += $row.Item("query_server")
write-host " > Query Server $($i):" $Destination_Server[$Destination_Server.length -1]
$sync_output += "_$Destination_Server.txt"
Invoke-ASCmd –InputFile $XML_file -Server $Destination_Server[$Destination_Server.length -1] >$sync_output
}
it does what it needs to do, except for the sync_output, I am getting this created:
as you can see, the first file,
CUBE_Destination_Server1
is perfectly created.
however, the second file, should be named
CUBE_Destination_Server2
but instead its for some reason appending the Destination_Server2 after appending Destination_Server1 twice...
Why is that?
The += operator appends. So this line:
$sync_output += "_$Destination_Server.txt"
takes the existing value of $sync_output and then adds "_$Destination_Server.txt" onto the end.
One way to get your desired result is to assign (not append) $syncResult inside your loop, with a statement like this:
$sync_output = (Join-Path $syncPATH $CUBE_input) + "_$Destination_Server.txt"
Update: it looks like you have a similar problem with the $Destination_Server variable--you should use plain assignment (=) with it too, rather than appending (also called concatenation) (+=).
The string is appending, because that is what you told it to do.
Straight up, the += says to add the new content at the end, so in the case of
$sync_output = Join-Path $syncPATH $CUBE_input
...
$sync_output += "_$Destination_Server.txt"
It sets a string, and then appends things to the end of that string, and keeps appending to it each time that command is executed when the ForEach-Object loop cycles. This is compounded by your other use of +=, which is appending objects into an array, since you setup $Destination_Server = #() which sets that variable as an empty array. So when you append to your string you are appending an entire array to that string.
So to replicate your example:
$sync_output = 'CUBE'
$destination_server = #()
Then inside the loop on the first pass you add a string to the array:
$destination_server += 'Destination_Server1'
So that array has 1 item in it. You then add that array to $sync_output which expanding the variables basically reads like:
"CUBE" += "_Destination_Server1.txt"
So now $sync_output has a value of CUBE_Destination_Server1.txt. Next iteration of the loop! You add another string to the $Destination_Server array:
$destination_server += 'Destination_Server2'
Now that array has 2 strings in it, so when you append it to the $sync_output string variable you essentially are doing this:
"CUBE_Destination_Server1.txt" += "_Destination_Server1Destination_Server2.txt"
It does that because the array of strings simply concocts all of the strings in its array into one string. In order to really fix this you need to be consistent and reference the last string in the array. I recommend not re-using the same variable for this, and will use $sync_output_file.
$sync_output = Join-Path $syncPATH $CUBE_input
$i=0
$Destination_Server = #()
foreach($row in $Table | where { $_.cube_name -match $CUBE })
{
$i++
$Destination_Server += $row.Item("query_server")
write-host " > Query Server $($i):" $Destination_Server[$Destination_Server.length -1]
$sync_output_file = $sync_output + '_' + $Destination_Server[-1] + '.txt'
Invoke-ASCmd –InputFile $XML_file -Server $Destination_Server[$Destination_Server.length -1] >$sync_output_file
}
Edit: Here's why $Destination_Server[$Destination_Server.length - 1] works like $Destination_Server[-1]:
In PowerShell an each item in an array has an index. That index is zero based. You can reference an item in the array by its index number as such $Array[X] where X is the index number of the item you are interested in. So given this array:
$MyArray = 'cat','dog','fish','goat','banana'
If you reference $MyArray[0] it will return cat, since that is the first item in the array. With arrays in PowerShell the .length and .count properties are synonymous, so when you reference $MyArray.length you are simply getting the count of items in the array. When you count items you start at 1, but array indexes start at 0, which is why you have to do .length - 1 to get the index number of the last item in the array. In my example if we do $MyArray.Length it would return 5, because my array has 5 items in it. So $MyArray[$MyArray.Length - 1] is essentially $MyArray[5 - 1], or $MyArray[4], which is the last item in $MyArray.
In addition to referencing items by index you can also use negative numbers, which will start at the end of the array, and count backwards. This method is not zero based, so $MyArray[-1] references the last item in the array, just like $MyArray[-2] is the second to the last item in the array.
So the difference between $MyArray[$MyArray.length - 1] and $MyArray[-1] is that in the first you calculate the index number for the last item in the array, where the second references the last item in the array regardless of what its index number is.
You can also specify ranges this way, so $MyArray[0..2] would get you the first 3 items in the array, and $MyArray[-2..-1] would get you the last 2 items in the array. That doesn't really apply to your situation right now, but it's handy to know in general and might be helpful in the future for you.

Compare Elements in Array for While Loop - Powershell

I am attempting to create a script to read a CSV, then perform some operations on the contents where the first field are similar. Right now I'm stuck on trying to set up the second While loop to compare the current element to the next one.
I'm fairly new to this, because I wasn't getting anywhere trying this in Java. I can't seem to find a combination of commands that will let the loop work.
Some things I've tried are:
While($csv[$count].ip -eq $csv[$count++].ip)
While((diff $csv[count].ip $csv[$count++].ip) = true)
While($csv[$count].ip = $csv[$count++].ip)
Don't use $count++ unless you want to actually change the value of $count itself. Instead use $count + 1 as the array index
$count = 0
while($count -le $csv.Count){
if($csv[$count].ip -eq $csv[$count + 1].ip){
# Do your stuff here
}
$count++
}

Powershell: multidimensional array changes return value from "system.object[]"

I wondered if anyone could shed some light on the issue I am facing when returning values from a multidimensional array through a function:
$ArrayList = #()
function MultiDimensionalArrayTest
{
param(
[array]$ArrayList
)
for($i = 0; $i -lt 1; $i++)
{
$ArrayModify += ,#("Test", "Test")
}
return $ArrayModify
}
$ArrayModify = MultiDimensionalArrayTest
foreach ($item in $ArrayModify)
{
Write-Host $item[0]
}
When the loop is executed once the values returned are:
T
T
However if the for statement is lopped twice, the values returned are:
Test
Test
My aim is to retrieve x amount of values "Test" from the Write-Host $item[0] regardless of how many times the statement is executed
It appears that if two or more rows are captured and returned in the $ArrayModify array, the value is a system.object[], however if looped once, the value "Test, Test" is captured and when Write-Host $item[0] is executed, it will print T.
Any advice would be greatly appreciated.
Not the cleanest way of dealing with it but you need to prevent PowerShell from unrolling the single element array into an array with two elements.
function MultiDimensionalArrayTest{
$ArrayModify = #()
for($i = 0; $i -lt 1; $i++){
$ArrayModify += ,#("Test$i", "Test$i$i")
}
,#($ArrayModify)
}
Using the above function will get the desired output I believe. ,#($ArrayModify) ensures that the array is returned and not unrolled into its elements as you saw above.
$ArrayList = #()
$ArrayList = MultiDimensionalArrayTest
foreach ($item in $ArrayList){$item[0]}
Giving the output for $i -lt 1 in the loop
Test0
Giving the output for $i -lt 2 in the loop
Test0
Test1
Your Output
Concerning your output from your example with $i -lt 1 PowerShell is unrolling the array into a single dimension array with 2 elements "Test" and "Test". You are seeing the letter "T" since all strings support array indexing and will return the character from the requested position.
Other issues with code
Not to beat a dead horse but really look at the other answers and comments as they provide some tips as to some coding errors and anomalies of the code you presented in the question.
First of all I notice several mistakes in your code.
This line $ArrayList = #() is useless since you don't use the $ArrayList variable afterwards.
Regarding the MultiDimensionalArrayTest function, you declared an $ArrayList argument but you never use it in the function.
Finally when this line makes no sense $ArrayModify = MultiDimensionalArrayTest = $item, you probably meant $ArrayModify = MultiDimensionalArrayTest.
This is not a complete answer to your question, since I am not sure what you are trying to achieve, but take a look at this line:
$ArrayModify = MultiDimensionalArrayTest = $item
This makes no sense, since you are calling the function MultiDimensionalArrayTest and passing two arguments to it, which are "=" (powershell assumes this is a string) and $item (null object). Then you assign whatever is returned to $ArrayModify.
The reason "T" is outputted, is because you are outputting the first element of what is at the moment a string. "Test" is outputted when $item is an array of strings.

Resources