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.
Related
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++
}
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.
I'm attempting to ensure an array of unknown length is split in one-or-more arrays with a maximum of 20 elements.
I've searched around and found several answers for similar questions based on arrays containing number sequences (or maybe I'm just missing something), but nothing for an array of strings.
I've come up with a solution, but it seems a little convoluted and I feel it could probably be better.
For example, take this array -
$metricDefinitionsHash= [ordered]#{"this one" = "111"; "that one" = "222"; "another one" = "333"}
And for the sake of this example, that's say I want only a maximum of two elements -
$metricsQueryParts = #()
$counter = 0
$metricDefinitionsHash.Keys | ForEach-Object {
$index = [System.Math]::Floor($counter / 2)
if ($metricsQueryParts.Length -eq $index)
{
$metricsQueryParts += ""
$metricsQueryParts[$index] = #()
}
$metricsQueryParts[$index] += $_
$counter++
}
So now I have an array of arrays -
#(#("this one", "that one"), #("another one"))
But... Can I achieve my goal in a more efficient way?
Use a for loop, the range operator (..), and the unary array construction operator (,):
[array]$keys = $metricDefinitionsHash.Keys
$size = 3
$metricsQueryParts = #()
for ($i=0; $i -lt $keys.Count; $i+=$size) {
$metricsQueryParts += ,$keys[$i..($i+$size-1)]
}
[array]$keys casts the key/value collection .Keys to a generic array (so that the index operator can be used on it).
The expression $i..($i+$size-1) gives you the indexes from $i up to the index before $i+$size, i.e. the next $size elements from the array starting at position $i. If there are less than $size elements the expression will give just the remaining number of elements.
The unary array construction operator ensures that $keys[...] is appended to $metricsQueryParts as a nested array instead of just concatenating the two arrays.
I really like Ansgar's solution but the keys collection isn't an array and as such you can't use the array range syntax. Keys is an OrderedDictionaryKeyValueCollection. I have used out-string -stream to force the OrderedDictionaryKeyValueCollection to an array.
$keys = $metricDefinitionsHash.Keys | out-string -stream
$size = 2
$metricsQueryParts = #()
for ($i=0; $i -lt $keys.Count; $i+=$size) {
$metricsQueryParts += ,$keys[$i..($i+$size-1)]
}
In Powershell, I am trying to combine the elements of several arrays to create an array of unique strings. I need to combine every element from each array with every element in the other arrays. It is difficult to concisely explain what I mean, so it may be easier to show.
I start with a 2d-array that looks something like this:
$array = #('this', ('A','B','C'), ('1','2','3','4'), 'that')
I need to create an array, whose contents will look like this:
thisA1that
thisA2that
thisA3that
thisA4that
thisB1that
thisB2that
thisB3that
thisB4that
thisC1that
thisC2that
thisC3that
thisC4that
The length and number of arrays in the original array are variable, and I don't know the order of the items in the original array.
So far, I've tried a few methods, but my logic has been wrong. Here was my first attempt at a solution:
$tempList = #()
#get total number of resources that will be created
$total = 1
for($i=0; $i -lt $array.Count; $i++){$total = $total * $array[$i].Count}
# build each resource from permutations of array parts
for ($i = 0; $i -lt $array.Count; $i++)
{
for ($j = 0; $j -lt $total; $j++)
{
$tempList += #('')
$idx = $total % $array[$i].Count
# item may either be string or an array. If string, just add it. If array, add the item at the index
if ($array[$i] -is [array]){ $tempList[$j] += $array[$i][$idx] }
else { $tempList[$j] += $array[$i] }
}
}
In this example, my logic with the modulus operator was wrong, so it would grab the only the first index of each array every time. Upon further consideration, even if I fix the modulus logic, the overall logic would still be wrong. For example, if the second two arrays were the same size, I would get 'A' paired with '1' each time, 'B' with '2', etc.
I'm sure there is a way to do this, but I simply can't seem to see it.
I think the answer's to use recursion so you can handle the fact that the array of arrays can be any length. This recursive function should:
take the first array from the array-of-arrays
loop through each item in that first array
if the first array is also the last array, just return each item.
if there are more arrays in the array-of-arrays then pass the remaining arrays to the recursive function
for each result from the recursive function, prefix the return value with the current item from the first array.
I think the code explains itself better than the above:
function Combine-Array {
[CmdletBinding()]
Param (
[string[][]]$Array
)
Process {
$current = $Array[0]
foreach ($item in $current) {
if ($Array.Count -gt 1) {
Combine-Array ([string[][]]#($Array | Select -Skip 1)) | %{'{0}{1}' -f $item, $_}
} else {
$item
}
}
}
}
Combine-Array #('this', ('A','B','C'), ('1','2','3','4'), 'that')
You can write pipeline function, which would add one more subarray into the mix. And then you call it as many times as many subarrays you have:
function CartesianProduct {
param(
[array] $a
)
filter f {
$i = $_
$a[$MyInvocation.PipelinePosition - 1] | ForEach-Object { $i + $_ }
}
Invoke-Expression ('''''' + ' | f' * $a.Length)
}
CartesianProduct ('this', ('A','B','C'), ('1','2','3','4'), 'that')
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++
}