This question already has answers here:
Why do integers in PowerShell compare by digits?
(4 answers)
Closed 6 years ago.
So this seems really simple, and it's really easy to guess the result, but I seem to be getting a really wierd result in powershell.
So essentially I'm building an array with an unknown number of objects in there, then running an operator against the .Count property.
Example:
$a = New-Object System.Collections.ArrayList
$n = 0
while ($n -ne 27) {$n++; $a.Add("Test line")}
# Array built, the .Count property should be 27
$bool = $false
$number = 2
if ($number -gt $a.Count) {$bool = $true}
# This correctly gives me $bool as $false
$number = 3
if ($number -gt $a.Count) {$bool = $true}
# This incorrectly gives me $bool as $true, and does so when $number is
# greater than 3.
Any ideas on this? I've never seen this before. Above is a simplified example, but essentially I'm pulling objects into an array, getting user input with Read-Host and I want to compare if the user input is greater than (-gt) the total count of the array.
Found the answer here: Why do integers in PowerShell compare by digits?
Added [int] markers to the variables and now works perfectly. Thanks for the suggestions.
Related
This question already has answers here:
How can I "zip" two arrays in PowerShell?
(2 answers)
Perform math operations with two equal length arrays
(2 answers)
Powershell foreach regarding multiple collections
(1 answer)
Is there a PowerShell equivalent of `paste` (i.e., horizontal file concatenation)? [duplicate]
(1 answer)
CMD or Powershell command to combine (merge) corresponding lines from two files [duplicate]
(6 answers)
Closed 6 months ago.
still very inexperienced in Powershell, but as is well known, every journey begins with the first steps.
I define two arrays in the script:
$array1 = #("server1", "server2")
$array2 = #("SID1", "SID2")
The SID1 and server1 belong together.
I then want to combine the two arrays using a loop:
Example:
foreach ($i in $array1) {
Write-Host "Server = ${i}"
}
How can I combine the two arrays?
Ideal is:
...
Write-Host "Server=${i}" and SID=${?}"
...
Can the two arrays be built into the foreach so that both values are filled when executing on the write host?
Thanks for your ideas and help.
Many greetings
Carlos
An alternative approach is to use [System.Linq.Enumerable]::Zip which provides convenient API(like python zip function). ie, You could do
$array1 = #("server1", "server2")
$array2 = #("SID1", "SID2")
$doSomethingWithPair = {
param($a, $b)
Write-Output "I have $a and $b"
}
[System.Linq.Enumerable]::Zip(
$array1,
$array2,
[Func[Object, Object, Object]]$doSomethingWithPair
)
This will print
I have server1 and SID1
I have server2 and SID2
Use a for loop to generate a range of valid indices for each array:
for($i = 0; $i -lt $array1.Count; $i++){
Write-Host "Server named '$($array1[$i])' has SID '$($array2[$i])'"
}
But a better solution would be to create a single array of objects that have both pieces of information stored in named properties:
$array = #'
Name,ID
Server1,SID1
Server2,SID2
'# |ConvertFrom-Csv
Now you can use a foreach loop without having to worry about the index:
foreach($computer in $array){
Write-Host "Server named '$($computer.Name)' has SID '$($computer.ID)'"
}
You could use a hash table or ordered dictionary (if you need the keys to keep their order), might be a good approach for your need.
Hash Table
$hash = #{
server1 = "SID1"
server2 = "SID2"
}
Ordered Dictionary
$dict = [ordered]#{
server1 = "SID1"
server2 = "SID2"
}
Then you can iterate over the key / value pairs either using .GetEnumerator():
foreach($pair in $hash.GetEnumerator()) {
Write-Host ("Server={0} and SID={1}" -f $pair.Key, $pair.Value)
}
Or by it's .Keys property:
foreach($key in $hash.PSBase.Keys) {
Write-Host ("Server={0} and SID={1}" -f $key, $hash[$key])
}
$array1 = #("server1", "server2")
$array2 = #("SID1", "SID2")
for($i=0; $i -lt $array1.Count; $i++){
Write-Host $array1[$i] ":"$array2[$i]
}
Can someone explain to me whats going on here? Its a piece of code i got from a script we use here at work and i believe that i understand why it counts but, from there im lost. Any generalization on why/how it does so would be greatly appreciated.
Please note, i did search everywhere before asking on here.
$gc = Get-ChildItem C:\users | Select-Object -ExpandProperty Name
$ls = #($gc)
$gcls = $ls.count
For($i=0; $I -lt $gcls; $i++){
Write-host "$($i): $($ls[$i])"
}
$selection = Read-Host "Enter Number"
$selection = $selection -split " "
$gc[$selection]
gc is self explanatory.
ls is as well throwing the output into an array
gcls is creating the variable to the list of counted strings
I kinda understand whats going on in the for statement where its setting $i to 0, saying if $i -lt the counted strings in $gcls (which it is due to $i=0), and it is counting the output. Now im still kind of following but, I just don't seem to understand how its outputting the strings the way it is.
Anyone familiar with this?
Lee_Dailey also answered this above as a comment.
Inlined comments explaining what each line does and where the count comes from, how the write-host works, etc.
$gc = Get-ChildItem C:\users | Select-Object -ExpandProperty Name #gets all items in c:\users
$ls = #($gc) #this seems redundant to me, but, puts output from get-childitem above into $ls
$gcls = $ls.count #stores a count of items found in get-childitem in $gcls
For($i=0; $I -lt $gcls; $i++){
<#
check out https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_for?view=powershell-7.1
specifically:
The For statement (also known as a For loop) is a language construct you can use to create a loop
that runs commands in a command block while a specified condition evaluates to $true.
So this will run the statement in the scriptblock (Write-host) while $i is less than $gcls (the count of items found in get-childitem).
each time it loops, it willll print $($i): $($ls[$i]) to the console and then increase $i by 1 (the $i++ in the For)
breaking down the print statement:
$($i) - prints the current loop count. The $() is a subexpression operator. It isnt really needed here, but it isnt hurting anything see https://ss64.com/ps/syntax-operators.html
$($ls[$i]) - we have a subexpression operator again. This time were printing a value in the variable $ls. The [$i] gets an item from the array. We need the $(), otherwise it would print all the contents of $ls rather than just the one item we wanted - try it yourself write-host "$($ls[0])" vs write-host "$ls[0]"
$ls[0] would get the first item in the array
$ls[1] would get the second, so on and so forth. Can see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_arrays?view=powershell-7.1 for more info
#>
Write-host "$($i): $($ls[$i])"
}
$selection = Read-Host "Enter Number" #prompts the user for input, expects INT seperated by spaces - 5 10
$selection = $selection -split " " #splits the user input
$gc[$selection] #prints the names using user input above. If the user enters 5, theyd get the 5th item returned by Get-ChildItem. Entering "5 10" would get the 5th and the 10th item. Again, see about_arrays above
I have an odd issue where a variable that is part of an if statement is not performing a comparison to a value as expected
The value is in a CSV file. It is a number, not a string so it should work fine. If the value comparison is set to -eq "0" it will print out the file entries that have only zero. If I use -gt it lists only the values entries not zero. So far so good.
If I use -le or -lt all file entries will get printed. If I use multiple comparisons such as -gt "0" and -lt "5" this also does not work - it prints out all values (except those equal to zero of course). I'm at a loss how to get the code to work properly (ignore Write-host, that is just so I can see the output).
I'm using Foreach because I'll never have more than about 10-20 entries, otherwise I'd go with Foreach-Object. Code details are below:
$extractFile = "C:\SrvInventory\DBLoginChk.csv"
$passwdexp = 5
$DBdetails = Import-csv $extractfile
Foreach ($DBCreds in $DBdetails) {
If (($DBCreds.DaysUntilExpiration -gt "0") -and
($DBCreds.DaysUntilExpiration -le $passwdexp)) {
$Expdt = $($DBCreds.DaysUntilExpiration)
$DBName = $($DBCreds.LoginName)
$Emailaddr = $DBName+'#'+'myplace.com'
Write-host $Expdt
}
}
File contents look like this:
"LoginName","DaysUntilExpiration"
"spot","35"
"pup","22"
"cat","21"
"rhino","3"
"camel","1"
"Bull","0"
"warthog","0"
"GViewer","0"
"grandrapids","0"
I've checked and tried a few of the suggestions on StackOverflow but none of them seem to work. I put together an example of what I am trying to accomplish.
[System.Random] $rand = New-Object System.Random
$randomNumbers = New-Object int[] 10;
[int[]] $randomNumbers;
for($i = 0; $i -lt $randomNumbers.Length; $i++)
{
($randomNumbers[$i] = $rand.Next(256)) 2>&1 | Out-Null;
}
I've tried the
> $Null
|Out-Null
2>&1
But none of them seem to suppress the output. It's showing 10 zero's in a row. One for each assignment. How can I suppress that output?
Remove int[]] $randomNumbers;. It is not the assignment that is printed, but the empty array.
other solution for replace your code ;)
[int[]] $randomNumbers=1..10 | %{ Get-Random -maximum 256 }
To complement Andrey Marchuk's effective answer:
[int[]] $randomNumbers looks like a type-bound PowerShell variable declaration, but, since no value is assigned, it is merely a cast: the preexisting value of $randomNumbers - a 10-element array of 0 values - is simply cast to [int[]] (a no-op in this case), and then output - yielding 10 lines with a 0 on each in this case.
A true type-bound assignment that is the (inefficient) equivalent of your New-Object int[] 10 statement is [int[]] $randomNumbers = #( 0 ) * 10.
Note that it is the presence of = <value> that makes this statement an assignment that implicitly creates the variable.
PowerShell has no variable declarations in the conventional sense, it creates variables on demand when you assign to them.
You can, however, use the New-Variable cmdlet to explicitly create variables, which allows you to control additional aspects, such as the variable's scope.
Variable assignments in PowerShell do NOT output anything by default, so there's no need to suppress any output (with | Out-Null, >$null, ...).
That said, you can force a variable assignment to output the assigned value by enclosing the assignment in (...).
$v = 'foo' # no output
($v = 'foo') # enclosed in () -> 'foo' is output
As you've discovered, actively suppressing the output in ($randomNumbers[$i] = $rand.Next(256)) 2>&1 | Out-Null; is unnecessary, because simply omitting the parentheses makes the statement quiet: $randomNumbers[$i] = $rand.Next(256)
Finally, you could simplify your code using the Get-Random cmdlet:
[int[]] $randomNumbers = 1..10 | % { Get-Random -Maximum 256 }
This single pipeline does everything your code does (not sure about performance, but it may not matter).
Trying to find the numbers in my file divisible by 3. How can I make my for each loop read each number individually?
this is my file:
6 9 7
-----
5 2 9
3 4 4
1 6 9
This is my code so far:
function number{
param($b)
# Loop through all lines of input
foreach($a in $b){
if ($line = % 3)
{
Write-Output "divisible by 3"
}
else {
Write-Output "number not divisible by 3"
}
}
}
#Variables
#Get input from csv file
$a = Import-Csv "document 2.Dat"
How have you got this far in, without realising that none of that even does anything at all? Whatever development approach you're using, you need to rethink it.
Hints that something is wrong:
How is it printing more dashes than there even are in the file? What is it actually printing? Use useful debugging/testing tool, wrap each thing in dashes so you can see where they start and end:
Oh that's broken.
Inside function number { put write-host 'hello?' and see that it's never printing anything.
Try calling the function by hand to see what it does:
Oh I have no idea what number is not divisible by 3, I'd better fix that so I can see what's going on.
And if you have an eye looking for details
where does $line get assigned? What is = doing in an if test? What is % 3 doing with nothing to the left of the %? Why am I using variable names like $a and $b which don't help me follow what's happening at all?
and, of course, "*why am I not write-host "..." all the way through, and/or stepping through this code in the debugger to see what's happening?
Google(*) "read file powershell"
Try it
That's my file alright. And the limits of the output are ... lines. Cool.
function number I should give it a better name but.
Sigh. alright, alright.
No output, even from a simple 'hi'? Ah, call the function.
Great.
Pass a parameter to it and print it...
No output.
Enough screenshots.
Pass a parameter when calling the function. Get-NumbersWhichDivideEvenlyByThree $FileContent
Iterate over the lines and print them inside the function.
Google "powershell get numbers from string" and stuff
Iteratively develop your code, going from working block to working block. Never end up in a position where you have a dozen lines that all don't work in half a dozen different ways all at once, and nowhere to go from there.
Bit you actually asked
Get numbers out of a string.
Use regex. This is exactly why they exist. But to try and keep it simple - in a way that's actually more complicated but tough - break the lines apart on spaces, and pick out the pieces which are numbers and throw the rest away.
To get this with a reasonably nice answer, you almost need to just magically know about -split, perhaps by stumbling on one of #mklement0's answers here about unary split or split has an unary form or the unary form of the -split operator is key here , or, I guess, have read help about_Split in careful detail.
-split '6 9 7' # this splits the line into parts on *runs* of whitespace
6
9
7 # look like numbers, but are strings really
So you get some text pieces, including the line of ----- in the file, that will be among them. And you need to test which are numbers and keep them, and which are dashes (letters, punctuation, etc) and throw those away.
$thing -as [int] # will try to cast $thing as a (whole) number, and silently fail (no exception) if it cannot.
# Split the line into pieces. Try to convert each piece to a number.
# Filter out the ones which weren't numbers and failed to convert.
$pieces = -split $line
$pieces = $pieces | ForEach-Object { $_ -as [int] }
$numbers = $pieces | Where-Object { $_ -ne $null }
Then you can do the % 3 test. And have code like:
function Get-NumbersWhichDivideEvenlyByThree {
param($lines)
foreach ($line in $lines)
{
$pieces = -split $line
$pieces = $pieces | ForEach-Object { $_ -as [int] }
$numbers = $pieces | Where-Object { $_ -ne $null }
foreach ($number in $numbers)
{
if (0 -eq $number % 3)
{
Write-Output "$number divisible by 3"
}
else
{
Write-Output "$number not divisible by 3"
}
}
}
}
$FileContent = Get-Content 'D:\document 2.dat'
Get-NumbersWhichDivideEvenlyByThree $FileContent
and output like:
(-split(gc D:\test.txt -Raw)-match'\d+')|%{"$_$(('',' not')[$_%3]) divisible by 3"}