Find the min element of Hashtable (Values are - DateTime) on PowerShell - powershell

There is a hashtable of view:
key(string) - value (dateTime)
Have to find the min value among Values (dateTime-s).
Can't find generic method to find such a value. The only way is smth like
$first_file_date = $dates_hash.Values | Measure-Object -Minimum -Maximum
Get-Date ($first_file_date);
Though visibly I get the result ($first_file_date) the actual value casts to GenericObjectMeasureInfo type and I can't cast it back to DateTime to work further.
Any ideas?

The value you're interested in is stored in the Minimum and Maximum properties of the object returned by Measure-Object:
$measurement = $dates_hash.Values | Measure-Object -Minimum -Maximum
# Minimum/oldest datetime value is stored here
$measurement.Minimum
# Maximum/newest datetime value is stored here
$measurement.Maximum
Use ForEach-Object or Select-Object if you want the raw value in a single pipeline:
$oldest = $dates_hash.Values | Measure-Object -Minimum | ForEach-Object -MemberName Minimum
# or
$oldest = $dates_hash.Values | Measure-Object -Minimum | Select-Object -ExpandProperty Minimum

To complement Mathias R. Jessen's helpful answer with an alternative solution based on LINQ:
# Sample hashtable.
$hash = #{
foo = (Get-Date)
bar = (Get-Date).AddDays(-1)
}
# Note that the minimum is sought among the hash's *values* ([datetime] instances)
# The [datetime[] cast is required to find the appropriate generic overload.
[Linq.Enumerable]::Min([datetime[]] $hash.Values)
Use of LINQ from PowerShell is generally cumbersome, unfortunately (see this answer). GitHub proposal #2226 proposes improvements.

Just use Sort-Object for this:
$dates_hash = #{
"a" = (Get-Date).AddMinutes(4)
"b" = (Get-Date).AddMinutes(5)
"c" = (Get-Date).AddMinutes(2)
"d" = (Get-Date).AddMinutes(5)
"e" = (Get-Date).AddMinutes(1)
"f" = (Get-Date).AddMinutes(6)
"g" = (Get-Date).AddMinutes(8)
}
$first_file_date = $dates_hash.Values | Sort-Object | Select-Object -First 1
Or if you want the whole object:
$first_file = $dates_hash.GetEnumerator() | Sort-Object -Property "Value" | Select-Object -First 1

Related

How to summarize value rows of one column with reference to another column in PowerShell object

I'm learning to work with the import-excel module and have successfully imported the data from a sample.xlsx file. I need to extract out the total amount based on the values of another column values. Basically, I want to just create a grouped data view where I can store the sum of values next to each type. Here's the sample data view.
Type Amount
level 1 $1.00
level 1 $2.00
level 2 $3.00
level 3 $4.00
level 3 $5.00
Now to import I'm just using the simple code
$fileName = "C:\SampleData.xlsx"
$data = Import-Excel -Path $fileName
#extracting distinct type values
$distinctTypes = $importedExcelRows | Select-Object -ExpandProperty "Type" -Unique
#looping through distinct types and storing it in the output
$output = foreach ($type in $distinctTypes)
{
$data | Group-Object $type | %{
New-Object psobject -Property #{
Type = $_.Name
Amt = ($_.Group | Measure-Object 'Amount' -Sum).Sum
}
}
}
$output
The output I'm looking for looks somewhat like:
Type Amount
level 1 $3.00
level 2 $3.00
level 3 $9.00
However, I'm getting nothing in the output. It's $null I think. Any help is appreciated I think I'm missing something in the looping.
You're halfway there by using Group-Object for this scenario, kudos on that part. Luckily, you can group by the type at your import and then measure the sum:
$fileName = "C:\SampleData.xlsx"
Import-Excel -Path $fileName | Group-Object -Property Type | % {
$group = $_.Group | % {
$_.Amount = $_.Amount -replace '[^0-9.]'
$_
} | Measure-Object -Property Amount -Sum
[pscustomobject]#{
Type = $_.Name
Amount = "{0:C2}" -f $group.Sum
}
}
Since you can't measure the amount in currency format, you can remove the dollar sign with some regex of [^0-9.], removing everything that is not a number, or ., or you could use ^\$ instead as well. This allows for the measurement of the amount and you can just format the amount back to currency format using the string format operator '{0:C2} -f ....
I don't know what your issue is but when the dollar signs are not part of the data you pull from the Excel sheet it should work as expected ...
$InputCsvData = #'
Type,Amount
level 1,1.00
level 1,2.00
level 2,3.00
level 3,4.00
level 3,5.00
'# |
ConvertFrom-Csv
$InputCsvData |
Group-Object -Property Type |
ForEach-Object {
[PSCustomObject]#{
Type = $_.Name
Amt = '${0:n2}'-f ($_.Group | Measure-Object -Property Amount -Sum).Sum
}
}
The ouptut looks like this:
Type Amt
---- ---
level 1 $3,00
level 2 $3,00
level 3 $9,00
Otherwise you may remove the dollar signs before you try to summarize the numbers.

PowerShell is re-arranging my columns and i cant figure out why

The code I am using is below:
Import-Csv -Path "E:\CSV\GA.csv" | Group-Object Product | ForEach {
New-Object PSObject -Property #{
Product = $_.Name
Stockcode = $_.Group[0].Stockcode
QuantityCounted = ($_.Group | Measure-Object -Property QuantityCounted -Sum).Sum
}
} | Export-Csv "E:\CSV\test.csv" -NoTypeInformation
The Headers should be in this order:
Product | Stockcode | QuantityCounted
If someone could point me in the right direction that would awesome, thank you.
Hash Tables are not ordered by default, if you need your object's properties to preserve their order you need to use an Ordered Dictionary instead.
The [ordered] attribute is introduced in PowerShell 3.0.
New-Object PSObject -Property ([ordered]#{
Product = 1
Stockcode = 2
QuantityCounted = 3
})
If you're running an up-to-date version of PowerShell, you can create objects with ordered properties using the [pscustomobject] type accelerator. This is more efficient and straight forward than New-Object.
The [pscustomobject] type accelerator was added in PowerShell 4.0.
[pscustomobject]#{
Product = 1
Stockcode = 2
QuantityCounted = 3
}

Custom object array element with minimum / maximum value of some property

$CustomObjects = Get-Random -Count 7 -InputObject #(0..300) | ForEach-Object {$i = 0} {
[PSCustomObject]#{
id = ($i++)
value = $_
}
}
$min, $max = & {$Args[0].Minimum, $Args[0].Maximum} ($CustomObjects | ForEach-Object value | Measure-Object -Minimum -Maximum)
$CustomObjects | Format-Table id, value -RepeatHeader
$CustomObjects | Where-Object {$_.value -eq $min} | Format-Table id, value
$CustomObjects | Where-Object {$_.value -eq $max} | Format-Table id, value
Are there more interesting options for finding the minimum / maximum?
We could come up with a long list of valid PowerShell statements that all look slightly different and would yield the same result, but there's basically 2 ways:
Sorting
Keeping count
Obtaining min/max through sorting is exactly what it sounds like:
$min,$max = #($CustomObjects |Sort Value)[0,-1].Value
# or
$min,$max = #($CustomObjects.Value |Sort)[0,-1]
Very succinct, easy to express - but might turn out to be slow on large collections
Obtaining min/max through keeping count is exactly what Measure-Object does:
$min,$max = [int]::MaxValue,[int]::MinValue
$CustomObjects |ForEach-Object {
$min = [Math]::Min($_.Value, $min)
$max = [Math]::Max($_.Value, $max)
}
Not as fancy, but faster for large collections
Mathias R. Jessen's helpful answer shows elegant - but potentially wasteful in terms of memory - solutions (Sort-Object of necessity needs to collect all input objects in memory so as to be able to determine the sort order).
Your own, Measure-Object-based approach takes advantage of the memory-throttling property of the pipeline.
Due to use of PowerShell's pipeline, both approaches are likely to be slow, however - see this answer for a discussion.
A simple optimization of your approach is to make the ForEach-Object value pipeline segment unnecessary by passing -Property value to Measure-Object:
$CustomObjects | Measure-Object -Property value -Minimum -Maximum
The Microsoft.PowerShell.Commands.GenericMeasureInfo returned by this command has .Minimum and .Maximum properties, so there's no strict reason to create separate $min and $max variables, but if you wanted to:
$measureResult = $CustomObjects | Measure-Object -Property value -Minimum -Maximum
$min, $max = $measureResult.Minimum, $measureResult.Maximum
If you'd rather not create an auxiliary variable, you can use the following, which uses the .ForEach() array method:
$min, $max = ($CustomObjects | Measure-Object -Property value -Minimum -Maximum).
ForEach({ $_.Minimum, $_.Maximum })

Compare-object output in variable

I'm building a script that will compare the last octed of in-use IPv4 addreses, with all the available octeds (2 till 254).
I am already this far that I do get a result by comparing array's, but my end-result is also an array, and I cannot seem to get only the number.
My script:
$guestIP = #("192.168.31.200","192.168.31.31","192.168.31.90","192.168.31.25","192.168.31.100")
$AllLastOcted = $("2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70","71","72","73","74","75","76","77","78","79","80","81","82","83","84","85","86","87","88","89","90","91","92","93","94","95","96","97","98","99","100","101","102","103","104","105","106","107","108","109","110","111","112","113","114","115","116","117","118","119","120","121","122","123","124","125","126","127","128","129","130","131","132","133","134","135","136","137","138","139","140","141","142","143","144","145","146","147","148","149","150","151","152","153","154","155","156","157","158","159","160","161","162","163","164","165","166","167","168","169","170","171","172","173","174","175","176","177","178","179","180","181","182","183","184","185","186","187","188","189","190","191","192","193","194","195","196","197","198","199","200","201","202","203","204","205","206","207","208","209","210","211","212","213","214","215","216","217","218","219","220","221","222","223","224","225","226","227","228","229","230","231","232","233","234","235","236","237","238","239","240","241","242","243","244","245","246","247","248","249","250","251","252","253","254")
$guestIP = $guestIP | sort -Property {$_-replace '[\d]'},{$_-replace '[a-zA-Z\p{P}]'-as [int]}
$AllLastOcted = $AllLastOcted | sort -Property {$_-replace '[\d]'},{$_-replace '[a-zA-Z\p{P}]'-as [int]}
$guestIP = $guestIP -replace('192.168.31.','')
Compare-Object -ReferenceObject ($AllLastOcted ) -DifferenceObject ($guestIP) | select -Property InputObject -First 1 | set-Variable AvailableOcted -PassThru
$AvailableOcted
My goal is, is that I have as result the first-available octed that I can use.
like:
write-host "IP that can be used is 192.168.31.$AvailableOcted"
PS > IP that can used is 192.168.31.2
You can simplify this a lot.
Instead of defining all numbers from 2 to 254 you can use the range operator to create the array. You also don't need the [int] casts. Instead of using the Compare-Object cmdlet to filter the octeds, you can use the Where-Object cmdlet:
$guestIP = #("192.168.31.200","192.168.31.31","192.168.31.90","192.168.31.25","192.168.31.100")
$AllLastOcted = 2 .. 254
$usedOcted = $guestIP -replace '.*\.'
$nextAvailableOcted = $AllLastOcted | Where { $_ -NotIn $usedOcted } | select -first 1
write-host "IP that can be used is 192.168.31.$nextAvailableOcted"
Output:
IP that can be used is 192.168.31.2
Well, as simple as:
$AvailableOcted.InputObject
would return only the value.
So it would look like this:
write-host ("IP that can be used is 192.168.31." + $AvailableOcted.InputObject)

Return object from array with highest value

I want to return an object from an array who's property has the highest value. Currently I am doing the following
Get-VM | Sort-Object -Property ProvisionedSpaceGB | Select-Object -Last 1
This works but is inefficient. I don't need the entire array sorted, I just need the object with largest value. Ideally I would use something like
Get-VM | Measure-Object -Property ProvisionedSpaceGB -Maximum
but this only returns the value of the object property, not the entire object. Is there a way to have measure-object return the base object?
Not directly. Measure-Object is intended to be an easy way to grab such values, not their input objects. You could get the maximum from Measure-Object and then compare against the array, but it takes a few steps:
$array = Get-VM
$max = ($array | measure-object -Property ProvisionedSpaceGB -maximum).maximum
$array | ? { $_.ProvisionedSpaceGB -eq $max}
You could also forgo Measure-Object entirely and iterate through the set, replacing the maximum and output as you go.
$max = 0
$array | Foreach-Object
{
if($max -le $_.ProvisionedSpaceGB)
{
$output = $_
$max = $_.ProvisionedSpaceGB
}
}
$output
This is a little dirtier so as to always return a single value. It would need a minor adjustment if you were to reuse it in a case where there may be multiple values that have the same maximum (filesize lengths when using Get-ChildItem, for example). It will replace $output with the latter iterate in a case where two or more objects have the same value for ProvisionedSpaceGB. You could turn $output into a collection easily enough to fix that.
I prefer the former solution myself, but I wanted to offer a different way to think about the problem.
You can use this:
$array = Get-VM | Sort-Object -Property ProvisionedSpaceGB -Descending
$array[0]