Conditional Logic and Casting in Powershell - powershell

I'm creating a script that imports 2 columns of CSV data, sorts by one column cast as type int, and shows only the values between 0 and 10,000. I've been able to get up to the sorting part, and I am able to show only greater than 0. When I try to add "-and -lt 10000" various ways, I am unable to get any useful data. One attempt gave me the data as if it were string again, though.
This only gives me > 0 but sorts as type int. Half way there!:
PS C:\> $_ = Import-Csv .\vc2.csv | Select -Property User_Name, Minutes; $_ | Sort {[int] $_.Minutes} | Where {($_.Minutes -gt 0)}
This gives me 10000 > x > 0 but sorts as string:
PS C:\> $_ = Import-Csv .\vc.csv | Select -Property User_Name, Minutes; $_ | Sort {[int] $_.Minutes} | Where {($_.Minutes -gt 0) -and ($_.Minutes -lt 10)}
Here and here are where I tried recasting as int and it gave me many errors:
PS C:\> $_ = Import-Csv .\vc.csv | Select -Property User_Name, Minutes; $_ | Sort {[int] $_.Minutes} | Where {[int]{($_.Minutes -gt 0) -and ($_.Minutes -lt 10000)}}
PS C:\> $_ = Import-Csv .\vc.csv | Select -Property User_Name, Minutes; $_ | Sort {[int] $_.Minutes} | Where { ({[int]$_.Minutes} -gt 0) -and ({[int]$_.Minutes} -lt 10000) }
Error: Cannot convert the "($.Minutes -gt 0) -and ($.Minutes -lt 10000)" value of type "System.Management.Automation.ScriptBlock" to type "System.Int32".
What is the proper syntax for this?

PowerShell usually coerces arguments of binary operators to the type of the left operand. This means when doing $_.Minutes -gt 10 the 10 gets converted to a string, because the fields in a parsed CSV are always strings. You can either switch the operands around: 10 -lt $_.Minutes or add a cast: [int]$_.Minutes -gt 10 or +$_.Minutes -gt 10.
Usually, when dealing with CSVs that contain non-string data that I want to use as such, I tend to just add a post-processing step, e.g.:
Import-Csv ... | ForEach-Object {
$_.Minutes = [int]$_.Minutes
$_.Date = [datetime]$_.Date
...
}
Afterwards the data is much nicer to handle, without excessive casts and conversions.

The problem is the use of the { and } brackets in the Where statement. Those are being interpreted as script blocks.
Where { ({[int]$_.Minutes} -gt 0) -and ({[int]$_.Minutes} -lt 10000) }
Try using ( and ) or excluding them altogether.
Where { (([int]$_.Minutes) -gt 0) -and (([int]$_.Minutes) -lt 10000) }
The way you're assigning values to $_ is also weird.

$_ represents the current value in the pipeline.
$list = #(1,2,3)
$list | foreach { $_ }
1
2
3
by assigning "$_" a value, you are losing that value as soon as you place it in the pipeline.
try something like:
$mycsv = import-csv .\vc.csv; $mycsv | select ...etc

Related

powershell winform searchbox shows results incorrect [duplicate]

I'm using Powershell to set up IIS bindings on a web server, and having a problem with the following code:
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if ($serverIps.length -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
$primaryIp = $serverIps[0]
$secondaryIp = $serverIps[1]
If there's 2+ IPs on the server, fine - Powershell returns an array, and I can query the array length and extract the first and second addresses just fine.
Problem is - if there's only one IP, Powershell doesn't return a one-element array, it returns the IP address (as a string, like "192.168.0.100") - the string has a .length property, it's greater than 1, so the test passes, and I end up with the first two characters in the string, instead of the first two IP addresses in the collection.
How can I either force Powershell to return a one-element collection, or alternatively determine whether the returned "thing" is an object rather than a collection?
Define the variable as an array in one of two ways...
Wrap your piped commands in parentheses with an # at the beginning:
$serverIps = #(gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort)
Specify the data type of the variable as an array:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
Or, check the data type of the variable...
IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
Force the result to an Array so you could have a Count property. Single objects (scalar) do not have a Count property. Strings have a length property so you might get false results, use the Count property:
if (#($serverIps).Count -le 1)...
By the way, instead of using a wildcard that can also match strings, use the -as operator:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
You can either add a comma(,) before return list like return ,$list or cast it [Array] or [YourType[]] at where you tend to use the list.
If you declare the variable as an array ahead of time, you can add elements to it - even if it is just one...
This should work...
$serverIps = #()
gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort | ForEach-Object{$serverIps += $_}
You can use Measure-Object to get the actual object count, without resorting to an object's Count property.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if (($serverIps | Measure).Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
Return as a referenced object, so it never converted while passing.
return #{ Value = #("single data") }
I had this problem passing an array to an Azure deployment template. If there was one object, PowerShell "converted" it to a string. In the example below, $a is returned from a function that gets VM objected according to the value of a tag. I pass the $a to the New-AzureRmResourceGroupDeployment cmdlet by wrapping it in #(). Like so:
$TemplateParameterObject=#{
VMObject=#($a)
}
New-AzureRmResourceGroupDeployment -ResourceGroupName $RG -Name "TestVmByRole" -Mode Incremental -DeploymentDebugLogLevel All -TemplateFile $templatePath -TemplateParameterObject $TemplateParameterObject -verbose
VMObject is one of the template's parameters.
Might not be the most technical / robust way to do it, but it's enough for Azure.
Update
Well the above did work. I've tried all the above and some, but the only way I have managed to pass $vmObject as an array, compatible with the deployment template, with one element is as follows (I expect MS have been playing again (this was a report and fixed bug in 2015)):
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
foreach($vmObject in $vmObjects)
{
#$vmTemplateObject = $vmObject
$asJson = (ConvertTo-Json -InputObject $vmObject -Depth 10 -Verbose) #-replace '\s',''
$DeserializedJson = (New-Object -TypeName System.Web.Script.Serialization.JavaScriptSerializer -Property #{MaxJsonLength=67108864}).DeserializeObject($asJson)
}
$vmObjects is the output of Get-AzureRmVM.
I pass $DeserializedJson to the deployment template' parameter (of type array).
For reference, the lovely error New-AzureRmResourceGroupDeployment throws is
"The template output '{output_name}' is not valid: The language expression property 'Microsoft.WindowsAzure.ResourceStack.Frontdoor.Expression.Expressions.JTokenExpression'
can't be evaluated.."
There is a way to deal with your situation. Leave most of you code as-is, just change the way to deal with the $serverIps object. This code can deal with $null, only one item, and many items.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
# Always use ".Count" instead of ".Length".
# This works on $null, only one item, or many items.
if ($serverIps.Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
# Always use foreach on a array-possible object, so that
# you don't have deal with this issue anymore.
$serverIps | foreach {
# The $serverIps could be $null. Even $null can loop once.
# So we need to skip the $null condition.
if ($_ -ne $null) {
# Get the index of the array.
# The #($serverIps) make sure it must be an array.
$idx = #($serverIps).IndexOf($item)
if ($idx -eq 0) { $primaryIp = $_ }
if ($idx -eq 1) { $secondaryIp = $_ }
}
}
In PowerShell Core, there is a .Count property exists on every objects. In Windows PowerShell, there are "almost" every object has an .Count property.

PowerShell returning 0 for length of string

I'm trying to get the length of the longest value in a PowerShell object. However, doing this results in $longenstPackageVersion keeping it's initial value of 0 -
$longenstPackageVersion = 0
$packages | Select-Object #{Name="PackageVersion"; Expression ={"$($_.properties.Id) $($_.properties.Version)"}} | ForEach-Object { if ($_.Length -gt $longenstPackageVersion) { $longenstPackageVersion = $_.Length } }
$longenstPackageVersion
So I tried outputting the length of every value, but nothing was output -
$packages | Select-Object #{Name="PackageVersion"; Expression ={"$($_.properties.Id) $($_.properties.Version)"}} | ForEach-Object { $_.Length }
Finally I tried to first convert each value to a string and then output it's length, but 0 was displayed for every value -
$packages | Select-Object #{Name="PackageVersion"; Expression ={"$($_.properties.Id) $($_.properties.Version)"}} | ForEach-Object { $_.ToString().Length }
Why is PowerShell unable to check the length of each value like this? And crucially, how can I amend my code to get it working as expected?
I found that I could output to an array, and then use Measure-Object to get the maximum length that I required.
$longenstPackageVersionLength = (($packages | ForEach-Object { "$($_.properties.Id) $($_.properties.Version)" }) | Measure-Object -Maximum -Property Length).Maximum

Powershell where-object | get-date

Hello i want to get only the month and year of a file with Date context
for example:
Where-Object {$_.StartDate-eq (Get-Date).AddDays(0).ToString("mm.yyyy") } |
current status:
Get-Content "file" -Encoding:String |
Select -Skip 1 |
ConvertFrom-Csv |
Where-Object {$_.StartDate -eq (Get-Date).ToString("MM.yyyy") } |
Where-Object {-not $_.DestinationNumber.StartsWith("+49") -and $_.DestinationNumber.StartsWith("+")} |
ForEach { [DateTime]$_.EndTime - [DateTime]$_.StartTime } |
ForEach { $Total=0 } { $Total += $_.TotalMinutes}
[math]::Round($Total,2),'Minuten'
i hope any body can help me.
Lower Case 'm' denotes minutes, you will have to use upper case M for month. Something like..
{$_.StartDate-eq (Get-Date).AddDays(0).ToString("MM.yyyy")
You don't need to invoke AddDays method, if u just have to get the current date. You can simple try,
(Get-Date).ToString("MM.yyyy")
--EDIT as per OP Clarification--
From the context it appears that you want to convert $_.StartDate to MM.yyyy format. If that's the case (assuming you have a valid date time value in $_.StartDate) you can use the Get-Date method on it. Something like,
Where-Object {(Get-Date($_.StartDate)).ToString("MM.yyyy") -eq (Get-Date).ToString("MM.yyyy") }

Exclude index in powershell

I have a very simple requirement of removing couple of lines in a file. I found little help on the net , where we can make use of Index. Suppose i want to select 5th line i use
Get-Content file.txt | Select -Index 4
Similarly, what if i dont need the 5th and 6th line? How would the statement change?
Get-Content file.txt | Select -Index -ne 4
I tried using -ne between -Index and the number. It did not work. neither did "!=".
Also the below code gives no error but not the desired output
$tmp = $test | where {$_.Index -ne 4,5 }
Pipeline elements does not have Index auto-property, but you can add it, if you wish:
function Add-Index {
begin {
$i=-1
}
process {
Add-Member Index (++$i) -InputObject $_ -PassThru
}
}
Then you can apply filtering by Index:
Get-Content file.txt | Add-Index | Where-Object Index -notin 4,5
Don't know about the Index property or parameter, but you can also achieve it like this :
$count = 0
$exclude = 4, 5
Get-Content "G:\input\sqlite.txt" | % {
if($exclude -notcontains $count){ $_ }
$count++
}
EDIT :
The ReadCount property holds the information you need :)
$exclude = 0, 1
Get-Content "G:\input\sqlite.txt" | Where-Object { $_.ReadCount -NotIn $exclude }
WARNING : as pointed by #PetSerAl and #Matt, ReadCount starts at 1 and not 0 like arrays
Try this:
get-content file.txt | select -index (0..3 + 5..10000)
It's a bit of a hack, but it works. Downside is that building the range takes some time. Also, adjust the 10000 to make sure you get the whole file.
Convert this as an array and use RemoveRange method(ind index, int count)
[System.Collections.ArrayList]$text = gc C:\file.txt
$text.RemoveRange(4,1)
$text

Powershell Select-Object from array not working

I am trying to seperate values in an array so i can pass them to another function.
Am using the select-Object function within a for loop to go through each line and separate the timestamp and value fields.
However, it doesn't matter what i do the below code only displays the first select-object variable for each line. The second select-object command doesn't seem to work as my output is a blank line for each of the 6 rows.
Any ideas on how to get both values
$ReportData = $SystemStats.get_performance_graph_csv_statistics( (,$Query) )
### Allocate a new encoder and turn the byte array into a string
$ASCII = New-Object -TypeName System.Text.ASCIIEncoding
$csvdata = $ASCII.GetString($ReportData[0].statistic_data)
$csv2 = convertFrom-CSV $csvdata
$newarray = $csv2 | Where-Object {$_.utilization -ne "0.0000000000e+00" -and $_.utilization -ne "nan" }
for ( $n = 0; $n -lt $newarray.Length; $n++)
{
$nTime = $newarray[$n]
$nUtil = $newarray[$n]
$util = $nUtil | select-object Utilization
$util
$tstamp = $nTime | select-object timestamp
$tstamp
}
Let me slightly modify the processing code, if it will help.
$csv2 |
Where-Object {$_.utilization -ne "0.0000000000e+00" -and $_.utilization -ne "nan" } |
Select-Object Utilization,TimeStamp
It will produce somewhat different output, but that should be better for working with.
The result are objects with properties Utilization and TimeStamp. You can pass them to the another function as you mention.
Generally it is better to use pipes instead of for loops. You don't need to care about indexes and it works with arrays as well as with scalar values.
If my updated code won't work: is the TimeStamp property really filled with any value?