In PowerShell v2, I'm trying to add only unique values to an array. I've tried using an if statement that says, roughly, If (-not $Array -contains 'SomeValue'), then add the value, but this only ever works the first time. I've put a simple code snippet that shows what I'm doing that doesn't work and what I've done as a workaround that does work. Can someone please let me know where my issue is?
Clear-Host
$Words = #('Hello', 'World', 'Hello')
# This will not work
$IncorrectArray = #()
ForEach ($Word in $Words)
{
If (-not $IncorrectArray -contains $Word)
{
$IncorrectArray += $Word
}
}
Write-Host ('IncorrectArray Count: ' + $IncorrectArray.Length)
# This works as expected
$CorrectArray = #()
ForEach ($Word in $Words)
{
If ($CorrectArray -contains $Word)
{
}
Else
{
$CorrectArray += $Word
}
}
Write-Host ('CorrectArray Count: ' + $CorrectArray.Length)
The Result of the first method is an array containing only one value: "Hello". The second Method contains two values: "Hello" & "World". Any help is greatly appreciated.
To fix your code, try -notcontains or at least WRAP your contains-test in parantheses. Atm. your test reads:
If "NOT array"(if array doens't exist) contains word.
This makes no sense. What you want is:
If array does not contain word..
That's written like this:
If (-not ($IncorrectArray -contains $Word))
-notcontains is even better, as #dugas suggested.
The first time around, you evaluate -not against an empty array, which returns true, which evaluates to: ($true -contains 'AnyNonEmptyString') which is true, so it adds to the array. The second time around, you evaluate -not against a non-empty array, which returns false, which evaluates to: ($false -contains 'AnyNonEmptyString') which is false, so it doesn't add to the array.
Try breaking your conditions down to see the problem:
$IncorrectArray = #()
$x = (-not $IncorrectArray) # Returns true
Write-Host "X is $x"
$x -contains 'hello' # Returns true
then add an element to the array:
$IncorrectArray += 'hello'
$x = (-not $IncorrectArray) # Returns false
Write-Host "X is $x"
$x -contains 'hello' # Returns false
See the problem? Your current syntax does not express the logic you desire.
You can use the notcontains operator:
Clear-Host
$Words = #('Hello', 'World', 'Hello')
# This will work
$IncorrectArray = #()
ForEach ($Word in $Words)
{
If ($IncorrectArray -notcontains $Word)
{
$IncorrectArray += $Word
}
}
Related
I have a problem with array as following with only one line:
$list = #()
$list = (("ResourceGroup","Vm1"))
$list | ForEach-Object -Parallel {
write-output $_[0] $_[1]
}
If I loop that array with one line, PowerShell prints the first 2 letter of each word. If I put 2 or more row like following:
$list = #()
$list = (("ResourceGroup","Vm1"),`
("ResourceGroup","Vm2")
)
PowerShell print correctly the values inside.
There is a way to print correctly the value of an array with only one line?
("ResourceGroup","Vm1") is interpreted as one array with two string elements, where as (("ResourceGroup","Vm1"), ("ResourceGroup","Vm2")) is interpreted as one array with 2 array elements, can be also called a jagged array. If you want to ensure that the first example is treated the same as the second example, you can use the comma operator ,:
$list = , ("ResourceGroup","Vm1")
$list | ForEach-Object -Parallel {
Write-Output $_[0] $_[1]
}
To put it in perspective:
$list = ("ResourceGroup","Vm1")
$list[0].GetType() # => String
$list = , ("ResourceGroup","Vm1")
$list[0].GetType() # => Object[]
Write-Output with the -NoEnumerate switch combined with the Array subexpression operator #( ) can be another, more verbose, alternative:
$list = #(Write-Output "ResourceGroup", "Vm1" -NoEnumerate)
$list | ForEach-Object -Parallel {
Write-Output $_[0] $_[1]
}
I have a PowerShell script, where I want to make sure certain variables have value before proceeding.
So I have the following:
$dataRow = $sheet.Cells.Find($country).Row
$serverCol = $sheet.Cells.Find($serverString).Column
$databaseCol = $sheet.Cells.Find($databaseString).Column
$userCol = $sheet.Cells.Find($userString).Column
$passwordCol = $sheet.Cells.Find($passString).Column
$partnerCol = $sheet.Cells.Find($partnerString).Column
#All variables in this array are required. If one is empty - the script cannot continue
$requiredVars = #($dataRow, $serverCol, $databaseCol, $userCol, $passwordCol, $partnerCol)
But when I foreach over the array like so:
foreach ($var in $requiredVars)
{
Write-Host DataRow = ($dataRow -eq $var)
Write-Host ServerCol = ($serverCol -eq $var)
Write-Host DatabaseCol = ($databaseCol -eq $var)
Write-Host UserCol = ($userCol -eq $var)
Write-Host PasswordCol = ($passwordCol -eq $var)
Write-Host PartnerCol = ($partnerCol -eq $var)
if ($var -eq $null)
{
[System.Windows.Forms.MessageBox]::Show("No data found for given string!")
$excel.Quit()
return
}
}
I always get the MessageBox. I added the "Write-Host" part to see the value of each variable, then changed it to see which variable was null but all variables have values in them and all the checks you see here return "False".
I'd like to know what I'm doing wrong and if the $requiredVars array only copies values, not references or something.
Instead of using separate variables, you may consider using a Hashtable to store them all.
This makes checking the individual items a lot simpler:
# get the data from Excel and store everything in a Hashtable
# to use any of the items, use syntax like $excelData.passwordCol or $excelData['passwordCol']
$excelData = #{
'dataRow' = $sheet.Cells.Find($country).Row
'serverCol' = $sheet.Cells.Find($serverString).Column
'databaseCol' = $sheet.Cells.Find($databaseString).Column
'userCol' = $sheet.Cells.Find($userString).Column
'passwordCol' = $sheet.Cells.Find($passString).Column
'partnerCol' = $sheet.Cells.Find($partnerString).Column
}
# check all items in the hash. If any item is $null then exit
foreach ($item in $excelData.Keys) {
# or use: if ($null -eq $excelData[$item])
if (-not $excelData[$item]) {
[System.Windows.Forms.MessageBox]::Show("No data found for item $item!")
$excel.Quit()
# IMPORTANT: clean-up used COM objects from memory when done with them
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet) | Out-Null
# Your code doesn't show this, but you'll have a $workbook object in there too
# [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
return
}
}
One way to directly solve your question is this:
$a = "foo"
$b = "bar"
$c = $null
$requiredVariables = $a, $b, $c
# How many total entries in array?
($requiredVariables).Count
# How many of them have a value?
($requiredVariables | Where-Object {$_}).Count
# So one option for a single check would be:
if (($requiredVariables.Count) -ne ($requiredVariables | Where-Object {$_}).Count) {
Write-Warning "Not all values provided"
}
However an alternative [and better] approach is to make your code in to a function that includes parameter validation
function YourCustomFunction {
Param (
[ValidateNotNullOrEmpty()]
$a
,
[ValidateNotNullOrEmpty()]
$b
,
[ValidateNotNullOrEmpty()]
$c
)
Process {
Write-Output "Your function code goes here..."
}
}
# Call your function with the params
YourCustomFunction -a $a -b $b -c $c
Example output:
Test-YourCustomFunction: Cannot validate argument on parameter 'c'. The argument is null or empty. Provide an argument that is not null or empty, and
then try the command again.
At line:39 char:48
I have a text file domains.txt
$domains = ‘c:\domains.txt’
$list = Get-Content $domains
google.com
google.js
and an array
$array = #(".php",".zip",".html",".htm",".js",".png",".ico",".0",".jpg")
anything in $domain that ends in something in #arr should NOT be in my final list
So google.com would be in final list but google.js would not.
I found some other stackoverflow code that give me the exact opposite of what I'm looking for but, hah I can't get it reversed!!!!
This gives me the exact opposite of what I want, how do I reverse it?
$domains = ‘c:\domains.txt’
$list = Get-Content $domains
$array = #(".php",".zip",".html",".htm",".js",".png",".ico",".0",".jpg")
$found = #{}
$list | % {
$line = $_
foreach ($item in $array) {
if ($line -match $item) { $found[$line] = $true }
}
}
$found.Keys | write-host
this gives me google.js I need it to give me google.com.
I've tried -notmatch etc and can't get it to reverse.
Thanks in advance and the more explanation the better!
Take the .s off, mash the items together into a regex OR, tag on an end-of-string anchor, and filter the domains against it.
$array = #("php","zip","html","htm","js","png","ico","0","jpg")
# build a regex of
# .(php|zip|html|htm|...)$
# and filter the list with it
$list -notmatch "\.($($array -join '|'))`$"
Anyway, the simple way to invert your result is to walk through $found.keys | where { $_ -notin $list }. Or to change your test to $line -notmatch $item.
But beware that you are doing a regex match and something like top500.org would match .0 and throw your results out. If you need to match at the end specifically, you need to use something like $line.EndsWith($item).
other solution
$array = #(".php",".zip",".html",".htm",".js",".png",".ico",".0",".jpg")
get-content C:\domains.txt | where {[System.IO.Path]::GetExtension($_) -notin $array}
I am parsing a bunch of data in a textfile. I get the data with Get-Content.
Then I loop through each row in $data. Split each row on a space and load those values into an array.
I then loop through each $string in the array.
If the $string matches a specific value I want to delete it out of the array.
$index.Delete(), $index.Remove() does not work, Here is what I have.
$data = Get-Content "C:\Users\$userName\Desktop\test-data.txt"
foreach($row in $data){
if($row)
{
[Array]$index = $row.Split(" ")
$i = 0
foreach($string in $index){
Write-Host $string
if($string -eq "value1" -or $string -eq "value2" -or $string -eq "value3")
{
$index.Delete() //This does not work.
}
}
I have also tried something like this as well but it just was not working out at all.
for($i -eq $index.length; $i -le 0; $i++)
{
Write-Host $index[$i] #this would hit once then give me an error saying the value is null
if($index[$i] -eq "value1" -or $index[$i] -eq "value2" -or $index[$i] -eq "value3")
{
$index.Remove() #does not hit here at all/nor will it work.
Write-Host $index
}
}
How do I remove something from the $index array..?
Is there a better way to do this?
Any help would be much appreciated, thanks.
The easiest way would be to chain -ne operators:
[Array]$index = $row.Split(" ") -ne $value1 -ne $value2 -ne $value3
Each one will remove all the elements of the array that match the value in the variable, and the result will be passed on to the next. When it's finished, the array will contain the elements the didn't match any of the $value variables.
Try this:
[array]$index = $row.Split(" ",[stringSplitOptions]::RemoveEmptyEntries) -notmatch "\b(?:$value1|$value2|$value3)\b"
I'm populating an array variable $array at some point in my code, for example like below
this
is
an
array
varaible
What if, I wanted to print out the array variable like thisisanarrayvariable as one liner
i took the below approach, but i'am not getting any out while the program is hanging
for ($i=0;$i -le $array.length; $i++)
{ $array[$i] }
obviuosly, i dont want to glue them together like $array[0]+$array[1]+$array[2]..
Hope i can get a better answer.
Joining array elements with no separator
Use the -join operator...
$array -join ''
...or the static String.Join method...
[String]::Join('', $array)
...or the static String.Concat method...
[String]::Concat($array)
For all of the above the result will be a new [String] instance with each element in $array concatenated together.
Fixing the for loop
Your for loop will output each element of $array individually, which will be rendered on separate lines. To fix this you can use Write-Host to write to the console, passing -NoNewline to keep the output of each iteration all on one line...
for ($i = 0; $i -lt $array.Length; $i++)
{
Write-Host -NoNewline $array[$i]
}
Write-Host
The additional invocation of Write-Host moves to a new line after the last array element is output.
If it's not console output but a new [String] instance you want you can concatenate the elements yourself in a loop...
$result = ''
for ($i = 0; $i -lt $array.Length; $i++)
{
$result += $array[$i]
}
The += operator will produce a new intermediate [String] instance for each iteration of the loop where $array[$i] is neither $null nor empty, so a [StringBuilder] is more efficient, especially if $array.Length is large...
$initialCapacity = [Int32] ($array | Measure-Object -Property 'Length' -Sum).Sum
$resultBuilder = New-Object -TypeName 'System.Text.StringBuilder' -ArgumentList $initialCapacity
for ($i = 0; $i -lt $array.Length; $i++)
{
$resultBuilder.Append($array[$i]) | Out-Null # Suppress [StringBuilder] method returning itself
}
$result = $resultBuilder.ToString()
Just use
-join $array
which will glue all elements together.