Powershell sort hashtable based on count - sorting

I have code that takes array $iis_stats and displays (1) the content NAME (2) number of occurrences for this content Value
$iis_stats | group | % { $h = #{} } { $h[$_.Name] = $_.Count } {$h}
How do I sort $h by value in descending order and print it?
I tried
$h | sort-object #{Expression={$_[1]}; Ascending=$false} {$h}
And I get error:
Sort-Object : A positional parameter cannot be found that accepts argument '$h'.
At D:\Script\parse_IIS_logs.ps1:45 char:6
+ $h | sort-object #{Expression={$_[1]}; Ascending=$false} {$h}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Sort-Object], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.SortObjectCommand
How to fix?

Like this?
$h.getenumerator() | sort value -descending

Related

Extract multiple columns from multiple test files in powershell

I got 450 files from computational model calculations for a nanosystem. Each of these files contain top three lines with Title, conditions and date/time. The fourth line has column labels (x y z t n m lag lead bus cond rema dock). From fifth line data starts upto 55th line. There are multiple spaces as delimiter. Spaces are not fixed.
I want to
I) create new text files with only x y z n m rema columns
Ii
II) I want only x y z and n values of all txt files in a single file
How to do it in powershell, plz help!
Based on your description, I guess the content of your files looks something like this:
Title: MyFile
Conditions: Critical
Date: 2020-02-23T11:33:02
x y z t n m lag lead bus cond rema dock
sdasd asdfafd awef wefaewf aefawef aefawrgt eyjrteujer bhtnju qerfqeg 524rwefqwert q3tgqr4fqr4 qregq5g
avftgwb ryhwtwtgqreg efqerfe rgwetgq ergqreq erwf ef 476j q4 w4th2 ef 42r13gg asdfasdrv
You can always read files like that by typing them out, line by line and only keep the lines you actually want. In your case, the data is in line 4-55 (including headers).
To get to that data, you can use this command:
Get-Content MyFile.txt | Select-Object -skip 3 -First 51
If you can confirm, that the data is the data you want, you can start working on the next issue - the multiple spaces delimiter issue.
Since (the number of) spaces are not fixed, you need to replace multiple spaces by a single space. Assuming that the values you are looking for are without spaces, you can add this to your pipeline:
Get-Content C:\MyFile.txt | Select-Object -skip 3 -First 51 | ForEach-Object {$_ -replace '( )+',' '}
The '( )+' part means one or more spaces.
Now you have proper csv data. To convert this to a proper object, you just need to convert the data from csv like this:
ConvertFrom-Csv -InputObject (Get-Content C:\MyFile.txt | Select-Object -skip 3 -First 51 | ForEach-Object {$_ -replace '( )+',' '}) -Delimiter ' '
From here it is pretty simple to select the values you want:
ConvertFrom-Csv -InputObject (Get-Content C:\MyFile.txt | Select-Object -skip 3 -First 51 | ForEach-Object {$_ -replace '( )+',' '}) -Delimiter ' ' | Select-Object x,y,z,n,m,rema
You also need to get all the files done, so you might start by getting the files like this:
foreach ($file in (Get-Content C:\MyFiles)){
ConvertFrom-Csv -InputObject (Get-Content $file.fullname | Select-Object -skip 3 -First 51 | ForEach-Object {$_ -replace '( )+',' '}) -Delimiter ' ' | Select-Object x,y,z,n,m,rema
}
You might want to split up the code into a more read-able format, but this should pretty much cover it.

find string with most occurrences in .txt file with powershell

I'm currently working on a school assignment in powershell and I have to display the word longer then 6 characters with the most occurences from a txt file. I tried this code but it's returning the number of occurrences for each word and it's not what i need to do. Please help.
$a= Get-Content -Path .\germinal_split.txt
foreach($object in $a)
{
if($object.length -gt 6){
$object| group-object | sort-object -Property "Count" -Descending | ft -Property ("Name", "Count");
}
}
From the question we don't know what's in the text file. The approaches so far will only work if there's only 1 word per line. I think something like below will work regardless:
$Content = (Get-Content 'C:\temp\test12-01-19' -raw) -Split "\b"
$content |
Where-Object{$_.Length -ge 6} |
Group-Object -Property Length -NoElement | Sort-Object count | Format-Table -AutoSize
Here I'm reading in the file as a single string using the -Raw parameter. Then I'm splitting on word boundaries. Still use Where to filter out words shorter than 6 characters. Now use Group-Object against the length property as seen in the other examples.
I don't use the word boundary RegEx very often. My concern is it might be weird around punctuation, but my tests look pretty good.
Let me know what you think.
You can do something like the following:
$a = Get-Content -Path .\germinal_split.txt
$a | Where Length -gt 6 | Group-Object -NoElement | Sort-Object Count -Descending
Explanation:
Where specifies the Length property's condition. Group-Object -NoElement leaves off the Group property, which contains the actual object data. Sort-Object sorts the grouped output in ascending order by default. Here the Count property is specified as the sorted property and the -Descending parameter reverses the default sort order.

How do I group and sum CSV rows by shared column value?

I'm new with powershell and I am currently stuck with an issue.
I import a CSV file with 2 columns (ServerName, and Size)
like this :
Server | Size
-------------
SRV1 | 140
SRV2 | 120
SRV1 | 100
SRV1 | 140
SRV2 | 200
I want to add all Size values for each server, for example:
SRV2 = 120+200
SRV1 = 140+100+140
I have no idea how to do it.
I tried with a for loop, but the operation is done for each line, so my results are false.
Could anyone help me ?
Use:
the Group-Object cmdlet to group the CSV rows by server name (Server)
then use Select-Object to construct a single output object per group,
containing the server name and the sum of all the associated rows' Size values, obtained via a calculated property that uses the Measure-Object cmdlet:
Import-Csv file.csv | Group-Object Server |
Select-Object Name, #{ n='Size'; e={ ($_.Group | Measure-Object Size -Sum).Sum } }
If you want the first output column to be named Server, replace Name with #{ n='Server'; e='Name' }
With your sample data, the above yields:
Name Size
---- ----
SRV1 380
SRV2 320
Here is an example how you could do it:
$Data = Import-Csv -Path "yourfilepath" -Delimiter ";"
$SortedData = $Data | Group {$_.server}
$Hashtable = #{}
$SortedData.group | ForEach-Object {
if ($Hashtable.Contains($_.server)) {
$Hashtable[$_.server] += ",$($_.size)"
} else {
$Hashtable.Add($_.server,$_.size)
}
}
You need to change your delimiter in your case

Iterate through txt files and find rows that are not in all files

I have folder with 3 text files.
File 1, call it test1.txt has values
11
22
22
test2.txt has values
11
22
22
33
test3.txt has values
11
22
22
33
44
44
How can I get my final result equal to (New.txt)
to be:
44
44
This values is not in the other 2 files so this is what I want.
So far code:
$result = "C:\NonDuplicate.txt"
$filesvalues=gci "C:\*.txt" | %{$filename=$_.Name; gc $_ | %{[pscustomobject]#{FileName= $filename; Row=$_ }}}
#list file where not exists others file with same value
$filesvalues | % {
$valtockeck=$_
[pscustomobject]#{
Val=$valtockeck
Exist=$filesvalues.Where({ $_.FileName -ne $valtockeck.FileName -and $_.Row -eq $valtockeck.Row }).Count -gt 0
}
} |
where Exist -NE $true |
% {$_.Val.Row | out-file $result -Append}
This is the error:
Where-Object : Cannot bind parameter 'FilterScript'. Cannot convert the "Exist" value of type "System.String" to type "System.Management.Automation.ScriptBlock".
At line:16 char:23
+ where <<<< Exist -NE $true |
+ CategoryInfo : InvalidArgument: (:) [Where-Object], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.WhereObjectCommand
try this
#list files/values couple
$filesvalues=gci "C:\temp\test\test*.txt" -file | %{$filename=$_.Name; gc $_ | %{[pscustomobject]#{FileName= $filename; Row=$_ }}}
#list file where not exists others file with same value
$filesvalues | % {
$valtockeck=$_
[pscustomobject]#{
Val=$valtockeck
Exist=$filesvalues.Where({ $_.FileName -ne $valtockeck.FileName -and $_.Row -eq $valtockeck.Row }).Count -gt 0
}
} |
where Exist -NE $true |
% {$_.Val.Row | out-file "c:\temp\test\New.txt" -Append}
$file1 = ".\test1.txt"
$file2 = ".\test2.txt"
$file3 = ".\test3.txt"
$results = ".\New.txt"
$Content = Get-Content $File1
$Content += Get-Content $File2
Get-Content $file3 | Where {$Content -notcontains $_}| Set-Content $Results
Other solution 1
#get couple files/values
$filesvalues=gci "C:\temp\test\test*.txt" -file |
%{$filename=$_.Name; gc $_ |
%{[pscustomobject]#{FileName= $filename; Row=$_ }}}
#group by value and filter by number of distinct filename, then extract data into file
($filesvalues | group -Property Row | where {($_.Group.FileName | Get-Unique).Count -eq 1 }).Group.Row |
out-file "C:\temp\test\New2.txt" -Append
The Compare-Object cmdlet's purpose is to compare two sets of inputs.
Nesting two Compare-Object calls yields the desired output:
$file1Lines = Get-Content .\test1.txt
$file2Lines = Get-Content .\test2.txt
$file3Lines = Get-Content .\test3.txt
(Compare-Object `
(Compare-Object -IncludeEqual $file1Lines $file2Lines).InputObject `
$file3Lines |
Where-Object SideIndicator -eq '=>'
).InputObject
Compare-Object outputs [pscustomobject] instances whose .InputObject property contains the input object and whose .SideIndicator property indicates which operand the value is unique to - <= (LHS) or >= (RHS) - and, with -IncludeEqual, if it is contained in both operands (==).
-IncludeEqual in the 1st Compare-Object call not only outputs the lines that differ, but also includes the ones that are the same, resulting in a union of the lines from file test1.txt and test2.txt.
By not specifying switches for the 2nd Compare-Object call, only [objects wrapping] the lines that differ are output (the default behavior).
Filter Where-Object SideIndicator -eq '=>' then filters the differences down to those lines that are unique to the RHS.
To generalize the command to N > 3 files and output to a new file:
# Get all input files as file objects.
$files = Get-ChildItem .\test*.txt
# I'll asume that all files but the last are the *reference files* - the
# files for which the union of all their lines should be formed first...
$refFiles = $files[0..$($files.count-2)]
# ... and that the last file is the *difference file* - the file whose lines
# to compare against the union of lines from the reference files.
$diffFile = $files[($files.count-1)]
# The output file path.
$results = ".\New.txt"
# Build the union of all lines from the reference files.
$unionOfLines = #()
$refFiles | ForEach-Object {
$unionOfLines = (Compare-Object -IncludeEqual $unionOfLines (Get-Content $_)).InputObject
}
# Compare the union of lines to the difference file and
# output only the lines unique to the difference file to the output file.
(Compare-Object $unionOfLines (Get-Content $diffFile) |
Where-Object SideIndicator -eq '=>').InputObject |
Set-Content $results
Note that Set-Content uses the Windows legacy single-byte encoding by default. Use the -Encoding parameter to change that.
Well, instead of writing the result in the $results file, save it in a variable $tmpResult and then do the same check as above for $tmpResult and $file3 to gain a final result. And if you have more than 3 files, you can create a loop to repeat the check.
But something is missing in the code above - you only get the unique lines in file2 and not those in file1.

Powershell Sort with Custom Sorting Expression

I have a directory containing numbered directories:
Archive
|-1
|-2
|-3
|-...
I need to create the next directory numerically. For which I am currently doing
$lastArchive = ls .\Archive | sort Name | select -Last 1
$dirName = '1'
if($lastArchive) {
$dirName = ([int]$lastArchive.Name)+1
}
This of course fails once we get to 10 which by sorting rules follows after 1 not 9. I need the sort expression to actually be [int]$_.Name - how would I do this?
I think you need to change that first line as follows:
$lastArchive = ls .\Archive |
Sort-Object -property #{Expression={[int]$_.Name}} |
Select-Object -Last 1
Then, you can create the next directory in numerical order like this:
mkdir ([int]$lastArchive.Name + 1).ToString()

Resources