Sorting movie titles into CSV - sorting

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

Related

Iterate through Hashtable containing List of values in Powershell

I am trying to print Keys and a list of values together in a single string in powershell.
I have something like this
List(dict(key:value)) (key= string , value=list<String>)
so my input is like this
dict(
"apple"=$list1
"banana"=$list2
"orange"=$list3)
$list1=#('red','green')
$list2=#('yellow','black')
$list3=#('orange')
Now I want output something like that:
$Final_ans= apple,red_green banana,yellow_black orange,orange
How can I do this in PowerShell? I am not able to iterate like this.
I tried few methods but it is giving me output System Collection.HashTable
Assuming you have a hashtable or dictionary where all keys are strings and the value entries are arrays of strings, like this:
$list1 = #('red','green')
$list2 = #('yellow','black')
$list3 = #('orange')
$hashtable = #{
"apple" = $list1
"banana" = $list2
"orange" = $list3
}
(#{} is PowerShell's native syntax for a hashtable (an unordered dictionary) literal.)
You can enumerate each key/value pair like this:
$hashtable.GetEnumerator() |ForEach-Object {
$_.Key # this will resolve to the key (ex. "apple")
$_.Value # this will resolve to the values (ex. #('red', 'green'))
}
So to construct a string like the one you describe, we can do something like this:
#($hashtable.GetEnumerator() |ForEach-Object {
$_.Key,($_.Value -join '_') -join ','
}) -join ' '
Here, we use the -join operator to concatenate the individual strings with different delimiters:
$_.Value -join '_' turns the value pairs (ex. #('red', 'green')) into a string like red_green
$_.Key,(...) -join ',' turns the key + string we created in the previous step into a string like apple,red_green
#(...) -join ' ' then turns all of those strings into one big space-separated string apple,red_green banana,yellow_black orange,orange

Insert string into multiple filenames

I have multiple files named in this format:
Fat1920OVXPlacebo_S20_R1_001.fastq
Kidney1235SHAM_S65_R1_001.fastq
Kidney1911OVXPlacebo_S94_R2_001.fastq
Liver1289OVXEstrogen_S24_R2_001.fastq
I need to insert the string "L1000_" into their names so that they read
Fat1920OVXPlacebo_S20_L1000_R1_001.fastq
Kidney1235SHAM_S65_L1000_R1_001.fastq
Kidney1911OVXPlacebo_S94_L1000_R2_001.fastq
Liver1289OVXEstrogen_S24_L1000_R2_001.fastq
I apologize but I have absolutely no experience in coding in powershell. The closest thing I could find to do this was a script that renames the entire file:
Set objFso = CreateObject(“Scripting.FileSystemObject”)
Set Folder = objFSO.GetFolder(“ENTER\PATH\HERE”)
For Each File In Folder.Files
sNewFile = File.Name
sNewFile = Replace(sNewFile,”ORIGINAL”,”REPLACEMENT”)
if (sNewFile<>File.Name) then
File.Move(File.ParentFolder+”\”+sNewFile)
end if
Next
however, I just need to insert a string at a specific place in the file's title. I have 257 files and do not want to go 1 by 1. Does anyone have an idea on how to run this in windows?
Use Get-ChildItem to enumerate the files of interest, pipe them to Rename-Item, and use a delay-bind script block ({ ... }) to dynamically determine the new name, via a regex-based -replace operation.
(Get-ChildItem $yourFolder -Filter *.fastq) |
Rename-Item -NewName { $_.Name -replace '(?<=_S\d+_)', 'L1000_' } -WhatIf
Note:
• The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
• Even though not strictly necessary in this case, enclosing the Get-ChildItem command in (...), the grouping operator ensures that already renamed files don't accidentally re-enter the enumeration of files to be renamed - see this answer.
(?<=_S\d+_) uses a positive look-behind assertion ((?<=...)) to match verbatim string _S, followed by one or more (+) digits (\d), followed by verbatim _.
Since the look-behind assertion merely matches a position in the string rather than a substring, the replacement operand, verbatim L1000_ in this case, is inserted at that position in (a copy of) the input string.
For a more detailed explanation of the delay-bind script-block technique, see this answer.
here's one way to do that with PoSh. note that the demo does not handle either the rename or directory related stuff. it ONLY handles generating the new file names.
what it does ...
fakes reading in a list of fileinfo objects
when ready to do this for real, replace the entire #region/#endregion block with a call to Get-ChildItem and save it to $FileList.
sets the text to be inserted
iterates thru the file list
splits the file .Name property on the underscores
saves that to a $Var
adds the 1st two splits, the insertion text, and the last two splits to a new array
joins that array with an underscore as the delimiter
sends the new file name to the $Result collection
displays the list of new names
the code ...
#region - fake reading in a list of files
# in real life, use Get-ChildItem
$FileList = #(
[system.io.fileinfo]'Fat1920OVXPlacebo_S20_R1_001.fastq'
[system.io.fileinfo]'Kidney1235SHAM_S65_R1_001.fastq'
[system.io.fileinfo]'Kidney1911OVXPlacebo_S94_R2_001.fastq'
[system.io.fileinfo]'Liver1289OVXEstrogen_S24_R2_001.fastq'
)
#endregion - fake reading in a list of files
$InsertionText = 'L1000'
$Result = foreach ($FL_Item in $FileList)
{
$FLI_Parts = $FL_Item.Name.Split('_')
($FLI_Parts[0,1] + $InsertionText + $FLI_Parts[2,3]) -join '_'
}
$Result
output ...
Fat1920OVXPlacebo_S20_L1000_R1_001.fastq
Kidney1235SHAM_S65_L1000_R1_001.fastq
Kidney1911OVXPlacebo_S94_L1000_R2_001.fastq
Liver1289OVXEstrogen_S24_L1000_R2_001.fastq
Using PowerShell, you could use a regular expression to rename the files. Example:
Get-ChildItem "C:\foldername\here\*.fastq" | ForEach-Object {
$oldName = $_.Name
$newName = [Regex]::Replace($oldName,'(S\d+)_(R\d+)','$1_L1000_$2')
Rename-Item $_ $newName -WhatIf
}
[Regex] is a PowerShell type accelerator for the .NET Regex class, and Replace is the method for the Regex class that performs text substitutions. The first parameter to the Replace method is the input string (the old filename), the second parameter is the regular expression pattern (run help about_Regular_Rxpressions for more information), and the third parameter is the replacement string pattern, where $1 is the first capture pattern in ( ), and $2 is the second capture pattern in ( )). Finally, the Rename-Item cmdlet renames the files. Remove the -WhatIf parameter if the output looks correct to actually perform the renames.

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.

PowerShell Input Validation - Input should NOT be ALL numbers

I have the following code that works well for validating length...
DO {
$NewID = Read-Host -Prompt " NEW ID NAME of object (8-15 chars) "
} UNTIL ($NewID.Length -gt 7 -and $WS_NewName.Length -lt 16)
How can I include code that ensures input contains either an ALPHA or ALPHANUMERIC string, but NOT a purely NUMERIC one?
This can be easily doable using regular expressions like that:
($NewID -match '^[A-z0-9]*$') -and ($NewID -notmatch '^[0-9]*$')
Short explanation: first expression looks for alpha/alphanumeric string and the second discards purely numeric entries.
By the way, in your example you use $NewID and then $WS_NewName in Until expression, that might be confusing (however, I assume you just forgot to change it while pasting here)

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.

Resources