Find and Copy files with wildcard in filename [closed] - powershell

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 months ago.
Improve this question
I have a big folder of files and I would like to find and copy specific files. There are no subfolders - all files are in the same path.
The problem is - a part of the file is always different and it is impossible to guess, so using the wildcard is required.
Here's a script:
dir K:\ALL_FILES /s/b|for /f %%i in ('find "First File To Copy.*.jpg"') do copy "%%i" k:\COPIED_FILES
dir K:\ALL_FILES /s/b|for /f %%i in ('find "Second File to be copied.*.jpg"') do copy "%%i" k:\COPIED_FILES
The above script works if I replace * with the real part of the file name.
I would like to do it in a batch file, but may be it would be easier to do with Powershell?

The fact that all your files are in a single folder makes this task easier, since most PowerShell cmdlets that take a -Path parameter accept string arrays for the parameter. So if you execute the cmdlet from the directory containing your files, the -Path array can contain your wildcard match patterns. This allows all the filtering to be done by the FileSystem Provider, which is faster than using -Include or -Exclude parameters or piping files to Where-Object.
$SourceDir = 'k:\ALLFILES`
$DestDir = 'k:\COPIED_FILES'
$MatchPatterns = #('First File To Copy.*.jpg','Second File To Copy.*.jpg')
Set-Location $SourceDir
Copy-Item -Path $MatchPattterns -Destination $DestDir
If you're just working interactively at the console, you can reduce the typing burden using aliases, positional parameters, and literal parameter values:
$MatchPatterns = #('First File To Copy.*.jpg','Second File To Copy.*.jpg')
sl k:\ALLFILES
copy $MatchPattterns k:\COPIED_FILES
And if you have a larger set of match patterns, a multiline Here-String makes for easier creation/editing:
$MatchPatterns = #'
First File To Copy.*.jpg
Second File To Copy.*.jpg
...
Tenth File to Copy.*.jpg
' -split "`n"
Documentation:
Copy-Item
Files in Subfilders: Recursive copy vs. Recursive file selection
While Copy-Item has the option of a -Recurse parameter, it doesn't recurse the file selection criteria through subfolders, instead, when the target of the copy command is a directory, it controls whether or not the contents of the directory is copied along with the folder itself.
If you want to select files from a folder and its subfolders, you want to select the files using Get-ChildItem with the -Recurse parameter, and then pipe the selected files for whatever further processing you choose, be it copy, move, or whatever. A simple example, selecting all text files in a folder and its subfolders, would look like this:
Get-ChildItem -Path 'C:\FolderPath' -Filter *.txt -Recurse
And while the options available to Get-ChildItem are sufficient for some scenarios, multiple criteria or matching more precise than wildcards require more advanced filtering. For those situations, you pipe the output of Get-ChildItem to Where-Object -- the "Swiss Army Knife" of object filtering in PowerShell. It can use any of the comparison operators available in PowerShell to test the properties of an object, or evaluate any script block that returns a boolean.
So, if your example contained subfolders with files you wanted to copy, you could use: (filename match patterns simplified for readability)
$SourceDir = 'k:\ALLFILES`
$DestDir = 'k:\COPIED_FILES'
Get-ChildItem -Path $SourceDir -Filter *.jpg -Recurse |
Where-Object { ($_.Name -like 'ABC.*.jpg') -or ($_.Name -like 'DEF.*.jpg')} |
Copy-Item -Destination $DestDir
And while the above Where-Object scriptblock does the trick for matching two different patterns, it could quickly become cumbersome for more than a few patterns. Using a regular expression for matching is more concise and more efficient. A regex comparison version of the above would be:
Get-ChildItem -Path $SourceDir -Filter *.jpg -Recurse |
Where-Object Name -match '(ABC|DEF)\..*\.jpg' |
Copy-Item -Destination $DestDir
You can see that the regex could be easily expanded to include more literal prefixes before becoming unwieldy. And of course, an even lengthier regex could be assigned to a string variable prior to the operation and the variable used in the operation itself.

Related

Find and Rename large quantities of files

I am using PowerShell to find, move, and rename a large amount of audit files. These files are in a shared folder with hundreds of gigabytes of extra junk. Manually clicking and dragging would take hours or even days as they are in many nested folders.
All files are currently named the same (audit.log, or audit1.log if there is a second log in the same folder). I need to find those files, copy them to a central location and rename them so they don't overwrite one another (not necessarily in that order).
I am not a programmer by any standard. This is what I have tried so far based on this website:
cd "H:\Flights\SCP\Log Analysis\1st Quarter"
Get-ChildItem -Filter "audit*.log" -Recurse `
| Rename-Item -NewName {$_.Name -replace 'audit', "$_.Fullname"} -WhatIf `
| Move-Item -Destination "H:\Flights\SCP\Log Analysis\Audit logs" -WhatIf
I use -WhatIf to make sure I do not make a mistake since I cannot overwrite the files. My original line of thought was to simply replace the word audit with the file path, but any reasonable method to rename the files in a way which will not overwrite will be helpful.
Theo and Mathias R. Jessen have provided all the crucial pointers in comments:
Rename-Item only accepts a mere name as a -NewName argument.
Move-Item can perform both moving and renaming in a single operation.
Delay-bind script blocks ({ ... }) can be passed to both Rename-Item's -NewName and Move-Item's -Destination parameters, which enable deriving the target name / path dynamically, for each input object ($_)
To put it all together:
Get-ChildItem -Filter audit*.log -Recurse |
Move-Item -Destination {
"H:\Flights\SCP\Log Analysis\Audit logs\$($_.FullName -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.
Note:
The target directory of the move operation must already exist (-Force does not create it for you, it would only allow you to replace an existing file).
$_.FullName -replace '[:\\/]', '_' transforms the full path of the original file into something that can be used as a file name, by replacing :, \ (and /) characters with _.
The caveat is that with long paths you may run into the 256-characters-per-name limit
An alternative is to use an abstract, unique identifier of fixed length, which you can generate with the New-Guid cmdlet, as Mathias suggests.

How to recursively append to file name in powershell?

I have multiple .txt files in folders/their sub-folders.
I want to append _old to their file names.
I tried:
Get-ChildItem -Recurse | Rename-Item -NewName {$_.name -replace '.txt','_old.txt' }
This results in:
Some files get updated correctly
Some files get updated incorrectly - they get _old twice - example: .._old_old.txt
There are few errors: Rename-Item : Source and destination path must be different.
To prevent already renamed files from accidentally reentering the file enumeration and therefore getting renamed multiple times, enclose your Get-ChildItem call in (), the grouping operator, which ensures that all output is collected first[1], before sending the results through the pipeline:
(Get-ChildItem -Recurse) |
Rename-Item -NewName { $_.name -replace '\.txt$', '_old.txt' }
Note that I've used \.txt$ as the regex[2], so as to ensure that only a literal . (\.) followed by string txt at the end ($) of the file name is matched, so as to prevent false positives (e.g., a file named Atxt.csv or even a directory named AtxtB would accidentally match your original regex).
Note: The need to collect all Get-ChildItem output first arises from how the PowerShell pipeline fundamentally works: objects are (by default) sent to the pipeline one by one, and processed by a receiving command as they're being received. This means that, without (...) around Get-ChildItem, Rename-Item starts renaming files before Get-ChildItem has finished enumerating files, which causes problems. See this answer for more information about how the PowerShell pipeline works.
Tip of the hat to Matthew for suggesting inclusion of this information.
However, I suggest optimizing your command as follows:
(Get-ChildItem -Recurse -File -Filter *.txt) |
Rename-Item -NewName { $_.BaseName + '_old' + $_.Extension }
-File limits the the output to files (doesn't also return directories).
-Filter is the fastest way to limit results to a given wildcard pattern.
$_.BaseName + '_old' + $_.Extension uses simple string concatenation via the sub-components of a file name.
An alternative is to stick with -replace:
$_.Name -replace '\.[^.]+$', '_old$&'
Note that if you wanted to run this repeatedly and needed to exclude files renamed in a previous run, add -Exclude *_old.txt to the Get-ChildItem call.
[1] Due to a change in how Get-ChildItem is implemented in PowerShell [Core] 6+ (it now internally sorts the results, which invariably requires collecting them all first), the (...) enclosure is no longer strictly necessary, but this could be considered an implementation detail, so for conceptual clarity it's better to continue to use (...).
[2] PowerShell's -replace operator operates on regexes (regular expressions); it doesn't perform literal substring searches the way that the [string] type's .Replace() method does.
The below command will return ALL files from the current folder and sub-folders within the current directory the command is executed from.
Get-ChildItem -Recurse
Because of this you are also re-turning all the files you have already updated to have the _old suffix.
What you need to do is use the -Include -Exclude paramters of the Get-Childitem Cmdlet in order to ignore files that already have the _old suffix, and meet your include criteria, for example.
Get-ChildItem -Recure -Include "*.txt" -Exclude "*_old"
Then pipe the results into your re-name item command
Get-ChildItem cmdlet explanation can be found here.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem?view=powershell-7

rename multiple folder to Uppercase not files using powershell

I am working on powershell where I have multiple folders i need to change all folders names into Uppercase not the files only folder.
I have tried the below code
Get-ChildItem -Path "C:\Users\Xyz\Desktop\sample" -Recurse | % {
if ($_.Name -cne $_.Name.ToUpper()) { ren $_.FullName $_.Name.ToUpper() }
}
But with this code it was changing only file name but I want to change only dir
for example
foldername (lowerCase)
abc
cab
dab
like this (UPPERCASE)
ABC
CAB
DAB
Thanks in advance
These tricks may not be obvious. How's this? Hmm, that didn't actually work. You can't rename folders to the same thing in upper case in powershell.
# doesn't work!
get-childitem -recurse -directory -path "C:\Users\Xyz\Desktop\sample" |
Rename-Item -NewName { $_.name.toupper() } -whatif
Sometimes calling cmd from powershell just works better. Try this first as a "whatif" to see if it does what you want. And if I really understand the question. All this does is echo strings. This command is just "pretend".
get-childitem -recurse -directory -path "C:\Users\Xyz\Desktop\sample" |
foreach { write-host cmd /c ren $_.fullname $_.name.toupper() }
And if that looks good, this actually does the rename. But maybe make a backup in case something goes wrong. Be able to undo the action.
get-childitem -recurse -directory -path "C:\Users\Xyz\Desktop\sample" |
foreach { cmd /c ren $_.fullname $_.name.toupper() }
To limit Get-ChildItem's output to directories only, use the -Directory switch (PSv3+; in PSv2, pipe to Where-Object { $_.PSIsContainer }
Apart from that, your solution should work, but doesn't due to a conceptual flaw in .NET's System.IO.DirectoryInfo.MoveTo() method (and also System.IO.Directory.Move() method), which PowerShell's Rename-Item cmdlet builds on, as of .NET Core 3.0 preview6 / .NET 4.8:
The method doesn't recognize case variations of directory name as a different name, and fails with Source and destination path must be different.
Curiously, files are not affected, as you've experienced.[1]
Of course, while NTFS is case-insensitive, it is also case-preserving, so it should be possible to rename foo to FOO, for instance.
The problem has been reported in this GitHub issue.
Workaround (PSv4+):
Note: js2010's helpful answer offers another, workaround, based on calling cmd.exe for each input folder to use its ren command. While it is conceptually simpler, the caveat is that this approach of creating a child process for every folder processed is inefficient and slow. That said, for occasional renaming operations that probably won't matter.
$path = 'C:\Users\Xyz\Desktop\sample'
Get-ChildItem -LiteralPath $path -Recurse -Directory |
Where-Object { $_.Name -cne $_.Name.ToUpper() } -PipelineVariable dir |
Rename-Item -PassThru -NewName { [IO.Path]::GetRandomFileName() } |
Rename-Item -NewName { $dir.Name.ToUpper() }
The workaround temporarily renames matching folders to a transient, randomly generated name ([IO.Path]::GetRandomFileName()) and then applies the all-uppercase version of the original name.
The input folder's original state and name are captured via variable $dir, which is "stashed" away for later use via the common -PipelineVariable parameter.
[1] Caveat: When you use Get-ChildItem or Get-Item to report a specific directory or file by its literal name, whatever case variation you specify is reported back, even though the true case variation as stored in the filesystem may differ (File Explorer, by contrast, always shows you the true case). To see the true case, useGet-ChildItem <parentDir> -Filter <name>or, as a quick workaround that may show additional items, however, append * to the name/path. In both cases, the name is treated as a wildcard pattern, and the result of the matching process reflects the true case.
How to double quotes folder names ? Some folders has spaces ...
write-host cmd /c ren $.fullname $.name.toupper()

Powershell Rename-Item issue with special characters

I've run into the issue I know has been addressed several times here previously but I'm not overly familiar with PS scripts or regular expressions and I'm struggling to implement a fix here.
Basically, I'd be very happy if this line of my script would work:
Get-childItem *.* -recurse -force | % {rename-item $_.name ($_.name -replace '(\d{2}) \[(\d{1})x(\d{2})\]','$1 s0$2e$3')}
And example file name would be "24 [1x01].avi" and should instead be named "24 s01e01.avi" - I'm trying to tidy up my media collection :)
I know the reason it doesn't is the square brackets in the file names. I think i have to move the files to a temp location, changing the name while doing so and then move back. My difficulty is that I haven't been able to find an example of this using the regular expression and I haven't been able to get this to work.
Also, is there a better workaround than this available yet? The bug on Microsoft Connect is closed as fixed?
Thanks!
I think your regular expressions might make more sense (to you), especially as a beginner, if you used "named groups" (a regular expression concept). I've modified your regular expression slightly to take this into account. You should really get familiar with regular expression terminology though, to ensure that you can update your regex to work in all scenarios.
"24 [1x01].avi" -replace '(?<ShowName>.*) \[(?<Season>\d{1})x(?<Episode>\d{2})\]','${ShowName} s0${Season}e${Episode}';
Result:
24 s01e01.avi
Can you give an example of a file name that doesn't work?
EDIT: Attaching example script. Let me know if this works for you.
# 1. Define a test folder path
$RootPath = "$env:SystemDrive\test";
# 2. Create the folder
mkdir -Path $RootPath;
# 3. Create a test file
Set-Content -Path "$RootPath\24 [1x01].txt" -Value '';
# 4. Get a list of files in the directory
$FileList = Get-ChildItem -Path $RootPath;
foreach ($File in $FileList) {
# 5. Fix up the name of each file
$NewName = $File.Name -replace '(?<ShowName>.*) \[(?<Season>\d{1})x(?<Episode>\d{2})\]','${ShowName} s0${Season}e${Episode}';
# 6. Rename the file
Move-Item -Path $File.FullName -Destination ((Split-Path -Path $File.FullName -Parent) + $NewName);
}
powershell Rename-Item fail to rename
If you are running PS 3+ add -LiteralPath switch to your rename:
One of the easiest ways to handle the Special Characters (such as square/block brackets[]) in the file-names, is to simply use the -LiteralPath parameter.
Error: When attempting to rename files or folders that contain square/block brackets [], the standard error message that PowerShell returns is "file not found", which is not accurate.
Reason: Windows still uses old fashioned 8.3 format short-file-names (max 8 chars with limited allowed chars) unfortunately PowerShell's -Path parameter (even in version 5.1) uses these internal names.
Solution: Use the -LiteralPath argument, available for most cmdlets (including Get-ChildItem or Rename-Item etc.)
Examples: Depicting handling of files or folders that contain square/block brackets []:
Get-ChildItem -LiteralPath "test[1].txt";
Test-Path -LiteralPath "C:\dir\test[1].txt";
Rename-Item -LiteralPath "test[1].txt" "test[2].txt";
Note: In PowerShell version below 3.0, to rename files/directories containing special characters, use Move-Item with -LiteralPath, instead of Rename-Item cmdlet because Rename-Item didn't have -LiteralPath in PS version 2.0 (or below).
Thanks to pointers from #Trevor Sullivan I was able to get the desired results by:
Updating to the most recent version of PowerShell (download link available in the comments)
Edited the script to the following:
Get-childItem *.* -recurse -force | Move-Item -Destination {$_ -replace '(\d{2}) \[(\d{1})x(\d{2})\]','$1 s0$2e$3'}

How to use wildcards in Windows Powershell V2.0

I'm trying to do a simple copy with rename from the Powershell V2.0 command line as follows:
Copy-Item my_file.* my_file_old.*
I expect this to function the same as the DOS equivalent
copy my_file.* my_file_old.*
However, it's failing with the complaint "illegal characters in path". If I replace the wildcard * with a specific file number, e.g. 1, it works fine.
I guess I'm basically trying to replicate that DOS version but in Powershell, where I'd like to use the -Exclude parameter once I get this basic version sorted out.
What am I doing wrong? Some posts on another forum I found suggested wildcards were not supported in V2.0, but I can't believe that. That's like Microsoft saying "We just released a new version of Windows, but forgot to support keyboards and mice".
If you look at the help on Copy-Item it will tell you which parameters support wildcards and which do not. -Destination does not support wildcards:
-Destination
Specifies the path to the new location. To rename a copied item, include the new name in the value.
Required? false
Position? 2
Default value
Accept pipeline input? true (ByPropertyName)
Accept wildcard characters? false
You can do what you want it with a tiny bit of script:
Get-ChildItem my_file.* | Copy-Item -Dest {$_.basename + "_old" + $_.extension} -WhatIf
Remove the -WhatIf parameter when you are happy it will copy the files correctly.
Wildcards are definitely supported, assuming you use them properly. A wildcard in your destination is strange and in most situations not what you want. And copying a file (or group of files) onto itself (which is what you're doing here - when you don't specify parameter names for Copy-Item the first is the source and the second is the destination) is almost certainly not what you want.
copy-item myFile.* -destination DESTPATH
or
get-childitem -filter myFile.* | Copy-Item -destination DESTPATH
Here is an example of renaming a bunch of .jpg files to a sequential list of numbers [001.jpg, 002.jpg, etc]
get-childitem *.jpg | foreach-object -begin {$count=1} -process {rename-item -path $_.FullName -newname ('{0:000}.jpg' -f $count++)}
You could adapt this to meet your needs by adjusting the *.jpg and '{0:000}.jpg' parts