Powershell sorting multiple columns and directions - sorting

I've managed to get Powershell to sort multiple columns and in different directions;
$prop1 = #{Expression='Priority'; Descending=$true }
$prop2 = #{Expression='Date'; Ascending=$true }
(Import-Csv $BackUpDataPath"WaitList.csv") |
Sort-Object $prop1, $prop2 |
Export-Csv $BackUpDataPath"WaitList.csv" -NoType
My problem is that the Priority value is stored as string data and sorts as text so the output to the file is;
Date Priority
6/28/2016 16:46 2
6/28/2016 16:59 2
6/29/2016 15:27 11
6/28/2016 16:42 1
6/28/2016 16:49 1
I've tried [int]$prop1 and similar ways to change it to an integer but it doesn't seem to work. The Line 6/29/2016 15:27 11 should be first but it isn't. Bare in mind as long as the #'s are less than 10 it sorts fine.

Try it with
$prop1 = #{Expression={[int]$_.Priority}; Descending=$true }

Looking how to convert from string->int i found [int] is not reliably, so I went for another solution. Please try something like this:
"2;2;11;1" -split ";" | %{[int]$Numeric=[convert]::ToInt32($_.toString(), 10) ; add-member -NotePropertyName "Numeric" -NotePropertyValue $Numeric -InputObject $_ ; echo $_} | Sort-Object Numeric
I create an String array, and then split it, for every object I generate the Int32 version of the value and then add a new member to the input object, I print this new object and finally just sort the array by the new property.
I tried to include the script inside the hashtable for the "property" parameter for sort-object but that didn't work :(.
Hope this can solve your problem.
Regards.

Related

Getting Local user objects and comparing them to a known good string?

Hi im currently working on a script to monitor back to an RMM tool, seem to be having issues converting my objects to match a "known string" inside my script.
ideally i'd like to poll the local computers local admin group then inline compare that with a string i've predefined, i was hoping to get the value, then just write a multi-lined string to match, then do some if statements to compare the 2.
$test3 = Get-LocalGroupMember -SID "S-1-5-32-544" | select -ExpandProperty Name | out-string
$test =#"
PC\Administrator
PC\test
"#
this is a little snippet, so the first one pulls the local ad group then saves it to a varible, and $test is my defined variable.
Both appear identical when outputted to console.
thanks so much in advance.
Instead of a predefined multiline string, Use either a string array or a hashtable to compare against.
The way you try to do it can fail the comparison simply because the items returned can be in a different order as in your predefined string.
Option 1: use an array
$testUsers = 'PC\Administrator', 'PC\test'
# this gets the users that are mentioned in the $testUsers array.
# if you want the opposite (users in the group, but NOT in the $testUsers array),
# change '-contains' into '-notcontains'
(Get-LocalGroupMember -SID "S-1-5-32-544").Name | Where-Object { $testUsers -contains $_ }
Option 2: use a Hashtable (a bit more work to set up, but extremely fast)
$testusers = #{
'PC\Administrator' = $true # the Values will not be used, so anything can go in here
'PC\test' = $true
}
# this gets the users that are mentioned in the $testUsers Hashtable.
# if you want the opposite (users in the group, but NOT in the $testUsers Hashtable),
# change '$testUsers.ContainsKey($_)' into '!$testUsers.ContainsKey($_)'
(Get-LocalGroupMember -SID "S-1-5-32-544").Name | Where-Object { $testUsers.ContainsKey($_) }
It's a bug in Windows where orphaned SIDs are left in the group. Try this instead:
$adminGroup = [ADSI]::new("WinNT://$env:COMPUTERNAME/$((Get-LocalGroup -SID S-1-5-32-544).Name)")
$adminGroupMembers = $adminGroup.Invoke('Members') |% {([ADSI]$_).Path.Replace('WinNT://', '')}
$adminGroupMembers | Out-String
You'll need to manipulate the output as required.

Length of string after delimiter in the data inside a file

I have a ton of data files that have delimiters inside and I would like to know the max length of the column after each delimiter. Since I can't to use a third-party program, I would like to get this done through PowerShell as that is built-in for Windows. And, at the same time I can't manually do. So, wondering if this could be achieved with PowerShell at all or any simple trick to do so?
Here is my sample data in a file FOO.TXT
Col1|Col2|Col3
12345|This is a String|This is another String
45688|String|This is another String of unknown length
30098|Second Column String|Third Column String
Expected output:
Col1 Max Length - 5
Col2 Max Length - 20
Col3 Max Length - 40
You can do it like this(but file with data must have csv extension):
$j=Import-Csv -Delimiter "|" -Path D:\testdir\new.csv #import our csv as array string
$colums=$j|gm -MemberType NoteProperty|Select-Object -ExpandProperty Name #get columns name
Foreach($colum in $colums){
$l=($j."$colum"|Measure-Object -Maximum -Property Length).Maximum #for each column get max length
Write-Host $colum" Max Length- "$l
}
Here is the answer I was looking for irrespective of the file extension. Thanks to both #Vad and #Theo.
$j=Import-Csv -Delimiter "|" -Path D:\testdir\FOO.TXT #import our csv as array string
$columns = $j[0].PSObject.Properties.Name #get columns name
Foreach($column in $columns){
$l=($j."$column"|Measure-Object -Maximum -Property Length).Maximum #for each column get max length
Write-Host $column" Max Length- "$l
}

Powershell Strange behaviour with Import-CSV

I have following powershell code:
clear;
$importedIDs = (Import-Csv "testing.csv" -Delimiter ';').id;
Write-Host $importedIDs.Length;
for ($i=0; $i -lt $importedIDs.Length; $i++) {
Write-Host $($importedIDs[$i]);
}
The goal is to read only the id column in the csv file which looks like this:
"created";"id"
"2018-04-04 21:03:01";"123456"
"2018-04-04 21:03:01";"123457"
When there are two or more rows the output is as expected:
2
123456
123457
However when there is only 1 row in the csv file (row with id 123456) the output is:
6
1
2
3
4
5
6
Desired output would be:
1
123456
Can anyone explain why this is happening and how can I fix this?
Any help is appreciated
If there are multiple rows in the csv you get an array of strings. One array-element per row. Therefore the index applies to the rows. If there is only one row you don't get a array containing one string, as you would probably expect. You get a single string instead. When using an index on a string powershell treats the string as an array of characters and therefore returns only one char.
You can slightly rewrite your script to get around the problem described by J. Bergmann.
Instead of using a for loop to loop through each element in the array, where an "element" may refer to a string in a string array or a character in a string, you can use a foreach loop and loop through elements in an array like this. foreach won't treat a string as an character array
clear;
$importedIDs = (Import-Csv "testing.csv" -Delimiter ';').id;
Write-Host $importedIDs.Length;
foreach ($importedID in $importedIDs) {
Write-Host $($importedID);
}

Find array elements which values are not part of another array PowerShell

I have two arrays
$adg - (A list of AD groups)
$dbs - (A list of database names)
Lets say I use this command
$adg -match $dbs.i
The output will be all the AD groups which have the string $dbs in its name.
However, I am aiming to find the DBs in which are not part of the AD groups array.
Eg:
$adg = #("Dev22_Owner","Test49_Owner","Rocket_Owner")
$dbs = #("Dev22", "Confidential", "InternDB", "Rocket", "Test49")
What approach should I take to get the output:
Confidential
InternDB
I tried $dbs | Where $adg -notmatch $dbs.i but there is no output.
I would first remove the unecessary user part from the ad group:
$groups = $adg | ForEach-Object {
$_ -replace '_.*?$'
}
Then you can use the Where-Object cmdlet with the -notin operator to filter them:
$dbs | Where-Object { $_ -notin $groups }
Output:
Confidential
InternDB
To offer a more concise PSv3+ alternative to Martin Brandl's helpful answer:
PS> (Compare-Object ($adg -replace '_Owner$') $dbs).InputObject
Confidential
InternDB
($adg -replace '_Owner$') returns a new array with copies of the input string that have _Owner suffix stripped from them.
Compare-Object compares the stripped array with the other array and returns objects that represent the differences between the two arrays.
Accessing the .InputObject on the difference objects returns the values that differ.
Note: If $dbs contained items that aren't in the stripped $adg array, the above would return a union of all differing items, irrespective of which side they're unique to; to distinguish the sides / limit to a side, you'd have to use the .SideIndicator property (value => indicates values exclusive to the right side, <= those exclusive to the left side).
As for what you tried:
$adg -match $dbs.i doesn't work as intended and is effectively the same as $adg -match '', which invariably returns all $adg items.
The reason is that array $dbs has no .i property, so the expression evaluates to $null, which on the RHS of match is coerced to a string and therefore is converted to the empty string.
Generally, the RHS of -match does not support arrays - only a single regular expression.
If you do supply an array, it is implicitly converted to a string by joining the elements to form a space-separated list; e.g. array 1, 2 is coerced to '1 2' and '1 2' -match (1, 2) therefore evaluates to $True.

Sorting movie titles into CSV

I've got a media server at home, and I've created a script that pulls all the file names and sorts them before putting them into a CSV. My only problem is that it sorts alphanumeric, but from a movie titles perspective, I'd like to ignore "A", "An", and "The". Is there a way to ignore those strings and have the sort work correctly without actually altering the file name in the CSV?
Yes, you can sort multiple objects into order by any property, and if none of the properties are quite what you want then you can provide a scriptblock to Sort-Object with some code "do xyz to each object" and it will sort them based on the output of the scriptblock - and that will only be used for sorting, it won't change anything.
So calculate the name without the leading words A, An, The using any code you want to. Here, I'm cooking with regex because it's quick, tasty and does case-insensitive matching by default:
Get-ChildItem | Sort-Object -Property { $_.Name -replace '^(A|An|The).' }
But you can do something just as effective with the plain ingredients around your kitchen:
Function Mangle-FilmName
{
param($file)
$name = $file.Name.ToLower()
if ($name.startswith('an'))
{
$name.Substring(4)
}
elseif ($name.startswith('the')
{
$name.Substring(5)
}
...
else
{
$name
}
}
Get-ChildItem | Sort-Object -Property Mangle-FilmName
Or with switch statements or loops over arrays of words, and/or/etc.
You could use something like the following.
Due to missing sample data I don't know the structure of your file names (word separators etc.) but you can customize the following code to your needs. What the code essentially does is splitting the base file name by separators '_', ' ' and '.', filters out your ignored words ('The', 'A', 'An' etc.) and joins back the parts to a single string.
Please note that at the end of this, the file names are compared without their initial word separators (i.e. 'The_Blue_House.mpg' and 'The.Blue.House.mpg' would be considered the same) which IMHO is a good thing but your needs may be different.
Hope that helps
$wordSeparator = '_| |\.'
$ignoredWords = #(
'The'
'A'
'An'
# add more
)
filter sortableFileName {
($_ -split $wordSeparator | ? { $_ -notin $ignoredWords }) -join ''
}
Get-ChildItem | Sort-Object { $_.BaseName | sortableFileName } # | Export-CSV

Resources