Powershell using where-object - powershell

I am using Powershell and am having trouble with the Where-Object cmdlet. I currently select * and then want to only output when a field is equal to Alabama. This field could be under any column, not just one.
This is what I have:
select * | where {$_.state_name -eq 'Alabama'} .
This works for state_name, but i cant get all columns without doing them individually. I've tried where{$_ -eq....} but that doesn't work.

Kind of a hack, but:
select * | where {($_ | ConvertTo-Csv -NoTypeInformation)[1] -like '*"Alabama"*'}

You have to iterate over all object's properties and check if it contains word 'Alabama'.
Example:
# Import CSV file and feed it to the pipeline
Import-Csv -Path .\My.csv |
# For each object
ForEach-Object {
# Psobject.Properties returns all object properties
# (Psobject.Properties).Value returns only properties' values
# -contains operator checks if array of values contains exact string 'Alabama'
# You can also use -like operator with wildcards, i.e. -like '*labama'
if(($_.PSObject.Properties).Value -contains 'Alabama')
{
# If any of the object properties contain word 'Alabama',
# write it to the pipeline, else do nothing.
$_
}
}

Related

Powershell Select-Object Property multiple comparisons same property

I am using Select-Object to filter a CSV to get the necessary columns.
When I use this: $Filter = $Csv | Select-Object ($Csv[0].PSObject.Properties.Name -like "*Results*" it filters all columns and displays everything containing results, this works fine. But how do I get it to keep my first column where the header is "Sample" as well as keeping the results? I have tried without success:
$Filter = $Csv | Select-Object ($Csv[0].PSObject.Properties.Name -like "*Results*" -and $Csv[0].PSObject.Properties.Name -like "Sample")
I understand you can add multiple properties comma separated but I am looking for the same property but with multiple matching parameters.
The output would include a column that have header name "Sample" and columns that would contain the word "Results". They both work individually in the first line of code provided, but how do i make it work together with both matching strings?
Edit: Expected output added
Select-Object's -Property parameter takes an array of property names (see documentation at Select-Object).
Your individual expressions ($Csv[0].PSObject.Properties.Name -like "*Results*" and $Csv[0].PSObject.Properties.Name -like "Sample") each individually return an array of matching columns as expected, but when you -and them together it becomes a boolean expression that returns $true or $false so your Select-Object becomes the equivalent of:
$Filter = $Csv | Select-Object -Property #( $true )
To demonstrate this, we'll use some sample data:
$csv = #"
Sample, Sample2, Something Else, A1Results, ResultsZ2
aaa, bbb, ccc, ddd, eee
"#
$data = $csv | ConvertFrom-Csv
and then see what your individual expressions return:
PS> ($data[0].PSObject.Properties.Name -like "*Results*")
A1Results
ResultsZ2
PS> ($data[0].PSObject.Properties.Name -like "Sample")
Sample
and now we'll try your combined expression:
PS> ($data[0].PSObject.Properties.Name -like "*Results*") -and ($data[0].PSObject.Properties.Name -like "Sample")
True
If you want to generate the list of all matching columns you'll need to combine the two separate lists in a different way - you can add them together and PowerShell will return a new array that contains the two separate arrays concatenated:
PS> ($data[0].PSObject.Properties.Name -like "*Results*") + ($data[0].PSObject.Properties.Name -like "Sample")
A1Results
ResultsZ2
Sample
and then if you plug that back into your original code you get this:
PS> $data | Select-Object ( ($data[0].PSObject.Properties.Name -like "*Results*") + ($data[0].PSObject.Properties.Name -like "Sample") )
A1Results ResultsZ2 Sample
--------- --------- ------
ddd eee aaa
Cheeky Update
#mklement0 notes in their answer that the left-hand side of the + needs to be an array in order for the addition operator to trigger array concatenation. You can ensure this by coercing the result of the individual expressions into arrays using the Array Subexpression Operator like this:
PS> $data | Select-Object ( #($data[0].PSObject.Properties.Name -like "*Results*") + #($data[0].PSObject.Properties.Name -like "Sample") )
In order to pass multiple property (column) names to the (possibly positionally implied) -Property parameter of the Select-Object cmdlet using an expression ((...)), you must pass a flat array.
To that end, use + for concatenation, making sure that (at least) the LHS is an array.
The following places the Sample property first, using #(...), the array-subexpression operator to wrap it in an array:
$Csv |
Select-Object (#('Sample') + ($Csv[0].PSObject.Properties.Name -like "*Results*"))

Powershell sort two fields and and get latest from CSV

I am trying to find a way to sort a CSV by two fields and retrieve only the latest item.
CSV fields: time, computer, type, domain.
Item that works is below but is slow due to scale of CSV and I feel like there is a better way.
$sorted = $csv | Group-Object {$_.computer} | ForEach {$_.Group | Sort-Object Time -Descending | Select-Object -First 1}
As Lee_Dailey suggests, you'll probably have better luck with a hashtable instead, Group-Object (unless used with the -NoElement parameter) is fairly slow and memory-hungry.
The fastest way off the top of my head would be something like this:
# use the call operator & instead of ForEach-Object to avoid overhead from pipeline parameter binding
$csv |&{
begin{
# create a hashtable to hold the newest object per computer
$newest = #{}
}
process{
# test if the object in the pipeline is newer that the one we have
if(-not $newest.ContainsKey($_.Computer) -or $newest[$_.Computer].Time -lt $_.Time){
# update our hashtable with the newest object
$newest[$_.Computer] = $_
}
}
end{
# return the newest-per-computer object
$newest.Values
}
}

How to remove characters from an item in an array using PowerShell

I have an array of computer names, $computers, that are in a FQDN format. I want to trim all characters to the right of the first period and including the period.
Ex: server-01.mydomain.int = server-01
This is what I tried but it errors out.
$computers = Get-VM | Select Name
$computers = $computers.Substring(0, $computers.IndexOf('.'))
$computers
When you do |Select Name, PowerShell returns an object with a Name property, rather than only the value of each Name property of the input objects.
You could change it to Select -ExpandProperty Name and then iterate over each item in the array for the actual substring operation using a loop or ForEach-Object - although we can skip the first step completely:
$computers = Get-VM |ForEach-Object { $_.Name.Substring(0, $_.Name.Indexof('.')) }
Or another way.
$computers = Get-VM | ForEach-Object { ($_.Name -split ".")[0] }
Since you are always selecting the first string before the first "." you can just split at the dot and select the first element in the resulting array.

Extracting a portion of a string then using it to match with other strings in Powershell

I previously asked for assistance parsing a text file and have been using this code for my script:
import-csv $File -header Tag,Date,Value|
Where {$_.Tag -notmatch '(_His_|_Manual$)'}|
Select-Object *,#{Name='Building';Expression={"{0} {1}" -f $($_.Tag -split '_')[1..2]}}|
Format-table -Groupby Building -Property Tag,Date,Value
I've realized since then that, while the code filters out any tags containing _His or _Manual, I need to also filter any tags associated with _Manual. For example, the following tags are present in my text file:
L01_B111_BuildingName1_MainElectric_111A01ME_ALC,13-Apr-17 08:45,64075
L01_B111_BuildingName1_MainElectric_111A01ME_Cleansed,13-Apr-17 08:45,64075
L01_B111_BuildingName1_MainElectric_111A01ME_Consumption,13-Apr-17 08:45,10.4
L01_B333_BuildingName3_MainWater_333E02MW_Manual,1-Dec-16 18:00:00,4.380384E+07
L01_B333_BuildingName3_MainWater_333E02MW_Cleansed,1-Dec-16 18:00:00,4.380384E+07
L01_B333_BuildingName3_MainWater_333E02MW_Consumption,1-Dec-16 18:00:00,25.36
The 333E02MW_Manual string would be excluded using my current code, but how could I also exclude 333E02MW_Cleansed and 333E02MW_Consumption? I feel I would need something that will allow me to extract the 8-digit code before each _Manual instance and then use it to find any other strings with a {MatchingCode}
xxx_xxxx_xxxxxxxxxxx_xxxxxxxxxx_MatchingCode_Cleansed
xxx_xxxx_xxxxxxxxxxx_xxxxxxxxxx_MatchingCode_Consumption
I know there are the -like -contains and -match operators and I've seen these posts on using substrings and regex, but how could I extract the MatchingCode to actually have something to match to? This post seems to come closest to my goal, but I'm not sure how to apply it to PowerShell.
You can find every tag that ends with _Manual and create a regex pattern that matches any of the parts before _Manual. Ex.
$Data = Import-Csv -Path $File -Header Tag,Date,Value
#Create regex that matches any prefixes that has a manual row (matches using the value before _Manual)
$ExcludeManualPattern = ($Data | Foreach-Object { if($_.Tag -match '^(.*?)_Manual$') { [regex]::Escape($Matches[1]) } }) -join '|'
$Data | Where-Object { $_.Tag -notmatch '_His_' -and $_.Tag -notmatch $ExcludeManualPattern } |
Select-Object -Property *,#{Name='Building';Expression={"{0} {1}" -f $($_.Tag -split '_')[1..2]}}|
Format-table -GroupBy Building -Property Tag,Date,Value

How to skip empty cells in a csv when using PowerShell to import Email Addresses?

I am trying to run the following script to import email addresses in powershell:
Import-CSV "C:\AliasesTest.csv" | foreach-object {
Set-Mailbox -Display Name $_.Name -EmailAddresses #{add=$_.Alias1,$_.Alias2,$_Alias3}}
It works fine, unless the csv has an empty cell under one of the Alias columns, at which point the following error is produced:
"The address '' is invalid: "" isn't a valid SMTP address..:
How can I construct my script to just ignore empty cells when it comes across them?
Check each property (alias) to see if it is empty, and only add the ones with values to the array inside your hash table:
Import-CSV "c:\AliasesTest.csv" | ForEach-Object {
#Save the CSV row for use in another loop later
$CSV = $_
Set-Mailbox -DisplayName $_.Name -EmailAddresses #{add = ("Alias1","Alias2","Alias3" | ForEach-Object { $Csv.$_ } | Where-Object{$_}) }
}
What that craziness does is, create a new hashtable with a key "add" that has a value of a sub expression. The sub expression has an array of property names that you want to check that it iterates over, converting each name to the value of that property, then filters out the empty ones.
Use a Where-Object filter prior to ForEach-Object like this:
Import-CSV "C:\AliasesTest.csv" | Where-Object {
$_.Alias1 -and $_.Alias2 -and $_Alias3
} | foreach-object { $_ }
If the alias' are empty strings they will be skipped. You don't have to use -and for all of them, if just one value is fine change to use -or.
Filter the aliases to take out the empty ones:
Import-CSV "C:\AliasesTest.csv" | foreach-object {
$aliases = #($_.Alias1,$_.Alias2,$_Alias3) | where-object{$_};
Set-Mailbox -Display Name $_.Name -EmailAddresses #{add=$aliases}}