Rename files with Powershell if file has certain structure - powershell

I am trying to rename files in multiple folder with same name structure. I got the following files:
(1).txt
(2).txt
(3).txt
I want to add the following text in front of it: "Subject is missing"
I only want to rename these files all other should remain the same

Tip of the hat to LotPings for suggesting the use of a look-ahead assertion in the regex.
Get-ChildItem -File | Rename-Item -NewName {
$_.Name -replace '^(?=\(\d+\)\.)', 'Subject is missing '
} -WhatIf
-WhatIf previews the renaming operation; remove it to perform actual renaming.
Get-ChildItem -File enumerates files only, but without a name filter - while you could try to apply a wildcard-based filter up front - e.g., -Filter '([0-9]).*' - you couldn't ensure that multi-digit names (e.g., (13).txt) are properly matched.
You can, however, pre-filter the results, with -Filter '(*).*'
The Rename-Item call uses a delay-bind script block to derive the new name.
It takes advantage of the fact that (a) -rename returns the input string unmodified if the regex doesn't match, (b) Rename-Item does nothing if the new filename is the same as the old.
In the regex passed to -replace, the positive look-ahead assertion (?=...) (which is matched at the start of the input string (^)) looks for a match for subexpression \(\d+\)\. without considering what it matches a part of what should be replaced. In effect, only the start position (^) of an input string is matched and "replaced".
Subexpression \(\d+\)\. matches a literal ( (escaped as \(), followed by 1 or more (+) digits (\d), followed by a literal ) and a literal . (\.), which marks the start of the filename extension. (Replace .\ with $, the end-of-input assertion if you want to match filenames that have no extension).
Therefore, replacement operand 'Subject is missing ' is effectively prepended to the input string so that, e.g., (1).txt returns Subject is missing (1).txt.

Related

How to replace any character different from others previously specified?

The following code removes the ! from all the .txt files.
get-childitem *.txt | ForEach {Move-Item -LiteralPath $_.name $_.name.Replace("!","")}
I need to do this not only for the ! character but also for #, ,, ~, among others. My intention is to get a code that has the following rule: any character other than [a-z] and also [0-9] must be removed from the file names.
Assuming you want to rename the files in place:
Get-Childitem *.txt | Rename-Item -WhatIf -NewName {
($_.BaseName -replace '[^\d\p{L}]') + $_.Extension
}
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.
For renaming files in place, it is better to use Rename-Item than Move-Item.
Note that Rename-item is directly piped to, with the new file names getting dynamically calculated via a delay-bind script block.
Negated (^) character set ([...]) \d\p{L} matches all characters that are neither digits (\d) nor letters (p{L}).
Note:
p{L} also matches letters outside the ASCII range of Unicode characters, such as é, for instance; if you really want to limit matching to ASCII-range letters only, use a-z
Similarly, \d matches not just 0 through 9, but other Unicode characters that are considered digits (e.g., ৮ (BENGALI DIGIT EIGHT, U+09EE); to limit matching to 0 through 9, use 0-9.
PowerShell by default is case-insensitive, so if you wanted to match lowercase letters only, you'd have to use -creplace and p{Ll}.
By not specifying a replacement string, all matching characters are effectively removed.

Replace text in files within a folder PowerShell

I have a folder that contains files like 'goodthing 2007adsdfff.pdf', 'betterthing 2007adfdsw.pdf', and 'bestthing_2007fdsfad.pdf', I want to be able to rename each, eliminating all text including 2007 OR _2007 to the end of the string keeping .pdf and getting this result: 'goodthing.pdf' 'betterthing.pdf' 'bestthing.pdf' I've tried this with the "_2007", but haven't figured out a conditional to also handle the "2007". Any advice on how to accomplish this is greatly appreciated.
Get-ChildItem 'C:Temp\' -Name -Filter *.pdf | foreach { $_.Split("_2017")[0].substring(0)}
Try the following:
Get-ChildItem 'C:\Temp' -Name -Filter *.pdf |
Rename-Item -NewName { $_.Name -replace '[_ ][^.]+' } -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.
The above uses Rename-Item with a delay-bind script block and the -replace operator as follows:
Regex [_ ][^.]+ matches everything from the first space or _ char. (character set [ _]) through to the following literal . char. ([^.]+ matches one or more chars. other than (^) than .) - that is, everything from the first / _ through to the filename extension (excluding the .).
Note: To guard against file names such as _2017.pdf matching (which would result in just .pdf as the new name), use the following regex instead: '(?<=.)[_ ][^.]+'
By not providing a replacement operand to -replace, what is matched is replace with the empty string and therefore effectively removed.
The net effect is that input files named
'goodthing 2007adsdfff.pdf', 'betterthing 2007adfdsw.pdf', 'bestthing_2007fdsfad.pdf'
are renamed to
'goodthing.pdf', 'betterthing.pdf', 'bestthing.pdf'
Without knowing the names of all the potential files, I can offer this solution that is 100%:
PS> $flist = ("goodthing 2007adsdfff.pdf","betterthing 2007adfdsw.pdf","bestthing_2007fdsfad.pdf")
PS> foreach ($f in $flist) {$nicename = ($f -replace "([\w\s]+)2007.*(\.\w+)", '$1$2') -replace "[\s_].","." ;$nicename}
goodthing.pdf
betterthing.pdf
bestthing.pdf
Two challenges:
the underscore is actually part of the \w character class. So the alternative to the above is to complicate the regex or try to assume that there will always be only one '_' before the 2007. Both seemed risky to me.
if there are spaces in filenames, there is no telling if you might encounter more than one. This solution removes only the one right before 2007.
The magic:
The -replace operator enables you to quickly capture text in () and re-use it in variables like $1$2. If you have more complex captures, you just have to figure out the order they are assigned.
Hope this helps.

How do I strip part of a file name?

Suppose I have a file database_partial.xml.
I am trying to strip the file from "_partial" as well as extension (xml) and then capitalize the name so that it becomes DATABASE.
Param($xmlfile)
$xml = Get-ChildItem "C:\Files" -Filter "$xmlfile"
$db = [IO.Path]::GetFileNameWithoutExtension($xml).ToUpper()
That returns DATABASE_PARTIAL, but I don't know how to strip the _PARTIAL part.
You don't need GetFileNameWithoutExtension() for removing the extension. The FileInfo objects returned by Get-ChildItem have a property BaseName that gives you the filename without extension. Uppercase that, then remove the "_PARTIAL" suffix. I would also recommend processing the output of Get-ChildItem in a loop, just in case it doesn't return exactly one result.
Get-ChildItem "C:\Files" -Filter "$xmlfile" | ForEach-Object {
$_.BaseName.ToUpper().Replace('_PARTIAL', '')
}
If the substring after the underscore can vary, use a regular expression replacement instead of a string replacement, e.g. like this:
Get-ChildItem "C:\Files" -Filter "$xmlfile" | ForEach-Object {
$_.BaseName.ToUpper() -replace '_[^_]*$'
}
Ansgar Wiechers's helpful answer provides an effective solution.
To focus on the more general question of how to strip (remove) part of a file name (string):
Use PowerShell's -replace operator, whose syntax is:<stringOrStrings> -replace <regex>, <replacement>:
<regex> is a regex (regular expression) that matches the part to replace,
<replacement> is replacement operand (the string to replace what the regex matched).
In order to effectively remove what the regex matched, specify '' (the empty string) or simply omit the operand altogether - in either case, the matched part is effectively removed from the input string.
For more information about -replace, see this answer.
Applied to your case:
$db = 'DATABASE_PARTIAL' # sample input value
PS> $db -replace '_PARTIAL$', '' # removes suffix '_PARTIAL' from the end (^)
DATABASE
PS> $db -replace '_PARTIAL$' # ditto, with '' implied as the replacement string.
DATABASE
Note:
-replace is case-insensitive by default, as are all PowerShell operators. To explicitly perform case-sensitive matching, use the -creplace variant.
By contrast, the [string] type's .Replace() method (e.g., $db.Replace('_PARTIAL', ''):
matches by string literals only, and therefore offers less flexibility; in this case, you couldn't stipulate that _PARTIAL should only be matched at the end of the string, for instance.
is invariably case-sensitive in the .NET Framework (though .NET Core offers a case-insensitive overload).
Building on Ansgar's answer, your script can therefore be streamlined as follows:
Param($xmlfile)
$db = ((Get-ChildItem C:\Files -Filter $xmlfile).BaseName -replace '_PARTIAL$').ToUpper()
Note that in PSv3+ this works even if $xmlfile should match multiple files, due to member-access enumeration and the ability of -replace to accept an array of strings as input, the desired substring removal would be performed on the base names of all files, as would the subsequent uppercasing - $db would then receive an array of stripped base names.

How to replace first characters in a file name with a string?

I've been working on a script to maintain the archive from my IP camera DVR. My recording software outputs filenames formatted so that the first character is the camera number, followed by a date and time stamp.
ex. 1_2017-11-03_00-45-07.avi
I want to replace the first character with a string that represents the camera.
ex. DivertCam_2017-11-03_00-45-07.avi
So far, I have:
Get-ChildItem "D:\DivertCam\1_*.avi" |
Rename-Item -NewName {$_.Name -replace '1_?','DivertCam_'}
Luckily with -WhatIfand running a transcript, I was able to see that my results would be wrong:
What if: Performing the operation "Rename File" on target "Item: D:\DivertCam\1_2017-11-03_00-45-07.avi Destination: D:\DivertCam\DivertCam_20DivertCam_7-DivertCam_DivertCam_-03_00-45-07.avi"
I know it's just picking out every "1_". How can I make it after the the first instance of "1_", or read the filename like a string, split it into 3 arrays separated by "_" and then change the first array?
The -replace operator performs a RegEx match and replacement, so you can use RegEx syntax to do what you want. For you the solution is to include the 'beginning of string' characater ^ at the beginning of your match text. Since this is RegEx, the ? means the previous character may or may not exist, so what you are currently matching on is any character matching '1' which may or may not be followed by an underscore. A better version would simply be:
$_.name -replace '^1','DivertCam'
To put that in context with the rest of your line, it would be:
Get-ChildItem "D:\DivertCam\1_*.avi" | Rename-Item -NewName {$_.name -replace '^1','DivertCam'}
Keep in mind this only works for the -replace operator which uses RegEx (short for Regular Expression) matching, and not the .Replace() method that you may see used, which uses simple pattern matching.
This will replace everything before the first '_' with 'DivertCam' (note use of % (foreach) to operate on each file individually).
Get-ChildItem "D:\DivertCam\1_*.avi" | % {Rename-Item $_.FullName -NewName "DivertCam$($_.Name.Substring($_.Name.IndexOf('_')))" }

Renaming files with an index in the bracket

I'd like to write a short powershell script for renaming files like:
abc(1), abc(2), .., abc(10), .., abc(123), ..
to
abc(001), abc(002), .., abc(010), .., abc(123), ..
Any idea? :)
Try this:
Get-ChildItem abc* | Where {$_ -match 'abc\((\d+)\)'} |
Foreach {$num = [int]$matches[1]; Rename-Item $_ ("abc({0:000})" -f $num) -wh }
The Where stage of the pipeline is doing to two things. First, only filenames that match the specified pattern are passed along. Second, it uses a capture group to grab the numeric part of the name which is sitting in $matches[1].
The Foreach stage applies script to each item, represented by $_, passed into it. The first thing it does is to get the "numeric" part of the old filename. Then it uses Rename-Item (PowerShell's rename command) to rename from the old name represented by $_ to the new name that is computed using a formatting string "abc({0:000})" -f $num. In this case, the formatting directive goes in {} where 0 represents the position of the value specified after -f. The :000 is a formatting directive displays number with up to three leading zeros. Finally the -wh is short for -WhatIf which directs potentially destructive operations like Rename-Item to show what it would do without actually doing anything. Once you are satisfied the command is working correctly, remove the -wh and run it again.