I have a strange issue, this is my CSV:
Serveur;Carte;Cordon;IP;Mac;Vmnic ;Vmnic mac;Connect;Port
Dexter;eth1;405;172.16.5.117;00:24:e8:36:36:df;Vmnic0;00:50:56:56:36:df;sw-front-1;A1
Dexter;eth2;14;192.168.140.17;00:24:e8:36:36:e1;Vmnic1;00:50:56:56:36:e1; sw_eq_ds_1;3
;;;;;;;;
Gordon;eth1;404;172.16.5.124;b8:ac:6f:8d:ac:b4;Vmnic0;00:50:56:5d:ac:b4;;
Gordon;eth2;35;192.168.140.114;b8:ac:6f:8d:ac:b6;Vmnic1;00:50:56:5d:ac:b6;;
Gordon;eth3;254;192.168.33.10;b8:ac:6f:8d:ac:b8;Vmnic2;00:50:56:5d:ac:b8;;
So I imported it into an array with the following code:
$Serveur = #()
Import-Csv C:\Users\aasif\Desktop\myfile.csv -Delimiter ";" |`
ForEach-Object {
$Serveur += $_.Serveur
}
And to remove duplicate values I did this :
$Serveur = $Serveur | sort -uniq
So when I display my Array, I obtain these two values : Dexter and Gordon and a third null value
But I also get an empty value
The following code return 3
$Serveur.count
Why?
Thanks for your help
If you want exclude empty values you can do like this
$Serveur = $Serveur | ? { $_ } | sort -uniq
In case someone (like me) needs to remove empty elements from array, but without sorting:
$Serveur = $Serveur | Where-Object { $_ } | Select -Unique
You have an array with 3 elements, so the count is 3. The element you got from the line ;;;;;;;; isn't $null, but an empty string (""), so it counts as a valid element. If you want to omit empty elements from the array, filter them out as C.B. suggested.
On a more general note, I'd recommend against using the += operator. Each operation copies the entire array to a new array, which is bound to perform poorly. It's also completely unnecessary here. Simply echo the value of the field and assign the output as a whole back to a variable:
$csv = 'C:\Users\aasif\Desktop\myfile.csv'
$Serveur = Import-Csv $csv -Delim ';' | % { $_.Serveur } | ? { $_ } | sort -uniq
Related
This question already has an answer here:
Powershell: Piping output of pracl command to array
(1 answer)
Closed 1 year ago.
I am trying to add elements to array for filtering. after it goes through the loop the first time
I receive "Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'."
I have tried several methods to try and figure this out.
$JsonDB = Get-Content 'Q:\Technology\1AA\HardwareCollection.json' | Out-String | ConvertFrom-Json
foreach($client in $JsonDB)
{
if($client.HRSeparation -eq "No")
{
$ClientNotHRSeparated += $client
}
else
{
$ClientHRSeparated += $client
}
}
$JsonDB
Any help would be greatly appreciated, Thanks!!
ConvertFrom-Json parses a JSON string into PSObject(s).
Since you did not define $ClientNotHRSeparated and $ClientHRSeparated anywhere, but immediately start adding ($client) objects to it, in the first iteration your variable $ClientNotHRSeparated will become that client object.
The next time you do +=, you're trying to add an object to another object which does not work.
Define the variables on top of the script, preferably as List object that has a .Add() method.
$ClientNotHRSeparated = [System.Collections.Generic.List[object]]::new()
$ClientHRSeparated = [System.Collections.Generic.List[object]]::new()
Then in your loop use that as
$ClientNotHRSeparated.Add($client)
# same for $ClientHRSeparated
P.S. Using a List is much faster/better that adding to a simple array (#()), because when you add items to an array (which has a fixed length) with +=, the entire array needs to be rebuilt in memory, consuming memory and processing time
Although this works, you don't need a loop at all. Just do:
$ClientNotHRSeparated = $JsonDB | Where-Object { $_.HRSeparation -eq "No" }
$ClientHRSeparated = $JsonDB | Where-Object { $_.HRSeparation -ne "No" }
The first line can be rewritten as $JsonDB = Get-Content -Path 'Q:\Technology\1AA\HardwareCollection.json' -Raw | ConvertFrom-Json.
Switch -Raw makes the cmdlet read the content of the file as one single multilined string
The behavior of += is entirely dependent on the left-hand side operand. On the first assignment, the value of $ClientNotHRSeparated is $null, so the resulting operation is:
$ClientNotHRSeparated = $null + $someCustomPSObject
Which PowerShell evaluates as just:
$ClientNotHRSeparated = $someObject
On the second assigment, $ClientNotHRSeparated is no longer $null, and PowerShell instead of tries to identify an overload for + that works on two operands of type [PSObject], which is where it fails.
If you want += to perform array addition, define the two array variables ahead of time with an assignment of a resizable array (use the #() array subexpression operator):
$ClientNotHRSeparated = #()
$ClientHRSeparated = #()
$JsonDB = Get-Content 'Q:\Technology\1AA\HardwareCollection.json' | Out-String | ConvertFrom-Json
foreach ($client in $JsonDB) {
if ($client.HRSeparation -eq "No") {
$ClientNotHRSeparated += $client
}
else {
$ClientHRSeparated += $client
}
}
$JsonDB
Now += is unambiguous both the first time and subsequently - the left-hand side operand is an array in either case.
As an alternative to looping through the whole collection manually, consider using the .Where() extension method in Split mode:
$JsonDB = Get-Content 'Q:\Technology\1AA\HardwareCollection.json' | Out-String | ConvertFrom-Json
$ClientNotHRSeparated, $ClientHRSeparated = #($JsonDB).Where({$_.HRSeparation -eq 'No'}, 'Split')
Much faster and more concise :-)
I'm trying to find the row with an attribute that is larger than the other row's attributes. Example:
$Array
Name Value
---- ----
test1 105
test2 101
test3 512 <--- Selects this row as it is the largest value
Here is my attempt to '1 line' this but It doesn't work.
$Array | % { If($_.value -gt $Array[0..($Array.Count)].value){write-host "$_.name is the largest row"}}
Currently it outputs nothing.
Desired Output:
"test1 is the largest row"
I'm having trouble visualizing how to do this efficiently with out some serious spaghetti code.
You could take advantage of Sort-Object to rank them by the property "Value" like this
$array = #(
[PSCustomObject]#{Name='test1';Value=105}
[PSCustomObject]#{Name='test2';Value=101}
[PSCustomObject]#{Name='test3';Value=512}
)
$array | Sort-Object -Property value -Descending | Select-Object -First 1
Output
Name Value
---- -----
test3 512
To incorporate your write host you can just run the one you select through a foreach.
$array | Sort-Object -Property value -Descending |
Select-Object -First 1 | Foreach-Object {Write-host $_.name,"has the highest value"}
test3 has the highest value
Or capture to a variable
$Largest = $array | Sort-Object -Property value -Descending | Select-Object -First 1
Write-host $Largest.name,"has the highest value"
test3 has the highest value
PowerShell has many built in features to make tasks like this easier.
If this is really an array of PSCustomObjects you can do something like:
$Array =
#(
[PSCustomObject]#{ Name = 'test1'; Value = 105 }
[PSCustomObject]#{ Name = 'test2'; Value = 101 }
[PSCustomObject]#{ Name = 'test3'; Value = 512 }
)
$Largest = ($Array | Sort-Object Value)[-1].Name
Write-host $Largest,"has the highest value"
This will sort your array according to the Value property. Then reference the last element using the [-1] syntax, then return the name property of that object.
Or if you're a purist you can assign the variable like:
$Largest = $Array | Sort-Object Value | Select-Object -Last 1 -ExpandProperty Name
If you want the whole object just remove .Name & -ExpandProperty Name respectively.
Update:
As noted PowerShell has some great tools to help with common tasks like sorting & selecting data. However, that doesn't mean there's never a need for looping constructs. So, I wanted to make a couple of points about the OP's own answer.
First, if you do need to reference array elements by index use a traditional For loop, which might look something like:
For( $i = 0; $i -lt $Array.Count; ++$i )
{
If( $array[$i].Value -gt $LargestValue )
{
$LargestName = $array[$i].Name
$LargestValue = $array[$i].Value
}
}
$i is commonly used as an iteration variable, and within the script block is used as the array index.
Second, even the traditional loop is unnecessary in this case. You can stick with the ForEach loop and track the largest value as and when it's encountered. That might look something like:
ForEach( $Row in $array )
{
If( $Row.Value -gt $LargestValue )
{
$LargestName = $Row.Name
$LargestValue = $Row.Value
}
}
Strictly speaking you don't need to assign the variables beforehand, though it may be a good practice to precede either of these with:
$LargestName = ""
$LargestValue = 0
In these examples you'd have to follow with a slightly modified Write-Host command
Write-host $LargestName,"has the highest value"
Note: Borrowed some of the test code from Doug Maurer's Fine Answer. Considering our answers were similar, this was just to make my examples more clear to the question and easier to test.
Figured it out, hopefully this isn't awful:
$Count = 1
$CurrentLargest = 0
Foreach($Row in $Array) {
# Compare This iteration vs the next to find the largest
If($Row.value -gt $Array.Value[$Count]){$CurrentLargest = $Row}
Else {$CurrentLargest = $Array[$Count]}
# Replace the existing largest value with the new one if it is larger than it.
If($CurrentLargest.Value -gt $Largest.Value){ $Largest = $CurrentLargest }
$Count += 1
}
Write-host $Largest.name,"has the highest value"
Edit: its awful, look at the other answers for a better way.
I want to sort array only if it contains more than N elements, something like this:
$myArray | if $myArray.Count() > N -> | Sort-Object
How can I do this in one line?
You can just use an if statement and do everything in one line:
if ($myArray.Length -gt N) { $myArray = $myArray | Sort-Object }
But why would you wan't to do it? I would prefer it this way:
if ($myArray.Length -gt N)
{
$myArray = $myArray | Sort-Object
}
You might wan't to find a solution without an if statement (only pipeline) but I don't see a reason for that.
I've been struggling with this for a couple of days, and I'm not sure how to conquer it. I need to do the following:
Import a csv of users with the following values:
ID, Name, Region
Create an array based on the Region values that I can then use to populate with ID's and Names with that region, ie.
Array_SEA
AA_SCOM, Adam Andrews, SEA
Array_OAK
BB_SCOM, Bob Barker, OAK
Here's the code I've got right now:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist =#()
foreach ($vitem in $list2)
{
$arraylist += New-Object PsObject -Property #{'Array' = "Array_" + $vitem.bu}
}
foreach ($varray in $arraylist)
{
$arr = new-variable -Name $varray
$arr.value += $varray.array
$arr
}
This produces the following error for records with a duplicate regions:
New-Variable: A variable with name '#{Array=Array_SCA}' already exists.
I'm also getting the following when it tries to add values:
Property 'value' cannot be found on this object; make sure it exists and is settable.
I get that I'm not actually creating arrays in the second section, but I'm not sure how to pass the output of the variable to an array name without turning the variable declaration into the array name, if that makes sense.
I've tried the following with hash tables, and it gets closer:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist =#{}
foreach ($vitem in $list2){$arraylist[$vitem.bu] = #()}
foreach ($record in $list2)
{
$arraylist[$vitem.bu] += ($record.SCOMID,$record.Name,$record.BU)
Write-host "Array: "
$arraylist[$vitem.bu]
write-host ""
}
The output on this shows no errors, but it just keeps showing the added fields for all of the records for each iteration of the list, so I don't think that it's actually assigning each unique BU to the array name.
I like the hashtable-approach, but I would finetune it a little. Try:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist = #{}
foreach ($vitem in $list2){
if($arraylist.ContainsKey($vitem.BU)) {
#Array exists, add item
$arraylist[($vitem.BU)] += $vitem
} else {
#Array not found, creating it
$arraylist[($vitem.BU)] = #($vitem)
}
}
#TEST: List arrays and number of entries
$arraylist.GetEnumerator() | % {
"Array '$($_.Key)' has $($_.Value.Count) items"
}
You could also use Group-Object like:
$list2 = ipcsv .\TSE_Contact_List.csv | Group-Object BU
#TEST: List groups(regions) and number of entries
$list2 | % {
"Region '$($_.Name)' has $(#($_.Group).Count) items"
}
I am currently importing a CSV file which has a column that is all numbers. I am attempting to cast it as an int and only pull ones that are greater than 100. I have manually gone through this CSV file, and I can confirm that there are three rows with a greater-than-100% value. However, this always returns 0. What am I doing wrong?
$percentTooLarge = Import-Csv path\file.csv | Foreach-Object { $_.SumHoldingPercent = $_.SumHoldingPercent -as [int]; $_ } | Where-Object { $_.SumHoldingPercent -gt 100 } | Measure-Object
$numPercentTooLarge = $percentTooLarge.Count
Because of the way compare operators work in PowerShell, this should do the trick:
$percentTooLarge = Import-Csv path\file.csv |
Where-Object { 100 -lt $_.SumHoldingPercent} |
Measure-Object
Basically, PowerShell, when you compare things, tries to convert right to the type of left. If you put a value from ipcsv first - left will be a string. If you put a numeric first - it will convert the value from the CSV file to a number (it will be smart enough to keep the type big-enough ;))
I tested with this code:
#"
foo,bar,percent
alfa,beta,120.5
beta,gamma,99.9
foo,bar,30.4
works,cool,120.7
"# | ConvertFrom-Csv | where { 100 -lt $_.percent }
... and the results seems OK.
Looking at your conversation with #Shay Levy, I would use:
[System.Globalization.CultureInfo] $culture = "en-us"
So you can try something like :
([decimal]::parse($_.SumHoldingPercent ,$culture)) -gt 100