I'm trying to do a simple bulk file rename to remove non-standard characters (e.g. " 0123456789-_.") from the start of a filename and just add a string to the filename.
e.g. '12 -_myfilename.doc' would become '012345 - myfilename.doc'
...where 012345 is my study number. I've tried using the script below but keep on getting the following error when stepping through the script executing the trimstart line...
"'Name' is a ReadOnly" property"
I guess that this is not a trimstart problem but the way in which I'm attempting to gather the result from it.
Any help appreciated.
The relevant part of the code looks like...
$MyFileObject=0
if ($MyRecursiveFlag) {
Get-ChildItem $MyStudyPath -recurse | where {$_.extension -in ".xls",”.xlsx”,".xslt",".pdf",".doc",".docx",".xlsm",".xml",".htm",".ppt"}|
ForEach-Object{
#Check if start of the file is compliant
$mymatch = [Regex]::Match($_, '\d{5}\s-\s')
if ($mymatch.Success){
#Already renamed correctly so nothing else to do
Write-Host "Okay - no changes $($_.Name)" -ForegroundColor Green
} else {
#No, it's not compliant so let's remove any preceding numbers, spaces and dashes etc
$MyFileObject = $_
$MyFileObject.Name = $MyFileObject.Name.trimstart(" 0123456789-_.")
#...and rename the file
Rename-Item -LiteralPath $MyFileObject.FullName -NewName "$($MyStudyNumber + ' - ' + $MyFileObject.Name)"
Write-Host "Renamed to $($MyFileObject.Name)" -ForegroundColor Yellow
}
}
}
I'm have the same problem when using Windows 7 PowerShell v3 and 5.1
You cannot change the Name of the current Get-ChildItem object (in your case $MyFileObject).
This should work (part of your script):
$MyFileObject = $_
$FileName = $MyFileObject.Name.trimstart(" 0123456789-_.")
#...and rename the file
Rename-Item -LiteralPath $MyFileObject.FullName -NewName "$($MyStudyNumber + ' - ' + $FileName)"
Write-Host "Renamed to $FileName" -ForegroundColor Yellow
Related
I'm in the process of writing up a PowerShell script that can take a bunch of .TIF images, rename them, and place them in a new folder structure depending on the original file name.
For example, a folder containing the file named:
ABC-ALL-210316-0001-3001-0001-1-CheckInvoice-Front.TIF
would be renamed to "00011CIF.TIF", and placed in the following folder:
\20220316\03163001\
I've been trying to put together a code to perform this task, and I got one to work where I had two different "ForEach" methods. One would do a bunch of file renaming to remove "-" and shorten "CheckInvoiceFront" to "CIF" and such. Then the second method would again pull all .TIF images, create substrings of the image names, and create folders from those substrings, and then move the image to the new folder, shortening the file name. Like I said, it worked... but I wanted to combine the ForEach methods into one process. However, each time I try to run it, it fails for various reasons... I've tried to change things around, but I just can't seem to get it to work.
Here's the current (non-working) code:
# Prompt user for directory to search through
$sorDirectory = Read-Host -Prompt 'Input source directory to search for images: '
$desDirectory = Read-Host -Prompt 'Input target directory to output folders: '
Set-Location $sorDirectory
# Check directory for TIF images, and close if none are found
Write-Host "Scanning "$sorDirectory" for images... "
$imageCheck = Get-ChildItem -File -Recurse -Path $sorDirectory -include '*.tif'
$imageCount = $imageCheck.count
if ($imageCount -gt 0) {
Write-Host "Total number of images found: $imageCount"
""
Read-Host -Prompt "Press ENTER to continue or CTRL+C to quit"
$count1=1;
# Rename all images, removing "ABCALL" from the start and inserting "20", and then shorten long filetype names, and move files to new folders with new names
Clear-Host
Write-Host "Reformatting images for processing..."
""
Get-ChildItem -File -Recurse -Path $sorDirectory -include '*.tif' |
ForEach-Object {
Write-Progress -Activity "Total Formatted Images: $count1/$imageCount" -Status "0--------10%--------20%--------30%--------40%--------50%--------60%--------70%--------80%--------90%-------100" -CurrentOperation $_ -PercentComplete (($count1 / $imageCount) * 100)
Rename-Item $_ -NewName $_.Name.Replace("-", "").Replace("ABCALL", "20").Replace("CheckInvoiceFront", "CIF").Replace("CheckInvoiceBack", "CIB").Replace("CheckFront", "CF").Replace("CheckBack", "CB") |Out-Null
$year = $_.Name.SubString(0, 4)
$monthday = $_.Name.Substring(4,4)
$batch = $_.Name.SubString(12, 4)
$fulldate = $year+$monthday
$datebatch = $monthday+$batch
$image = $_.Name.SubString(16)
$fullPath = "$desDirectory\$fulldate\$datebatch"
if (-not (Test-Path $fullPath)) { mkdir $fullPath |Out-Null }
Move-Item $_ -Destination "$fullPath\$image" |Out-Null
$count1++
}
# Finished
Clear-Host
Write-Host "Job complete!"
Timeout /T -1
}
# Closes if no images are found (likely bad path)
else {
Write-Host "There were no images in the selected folder. Now closing..."
Timeout /T 10
Exit
}
Usually this results in an error stating that it's can't find the path of the original file name, as if it's still looking for the original non-renamed image. I tried adding some other things, but then it said I was passing null values. I'm just not sure what I'm doing wrong.
Note that if I take the everything after the "Rename-Item" (starting with "$year =") and have that in a different ForEach method, it works. I guess I just don't know how to make the Rename-Item return its results back to "$_" before everything else tries working on it. I tried messing around with "-PassThru" but I don't think I was doing it right.
Any suggestions?
As Olaf points out, situationally you may not need both a Rename-Item and a Move-Item call, because Move-Item can rename and move in single operation.
That said, Move-Item does not support implicit creation of the target directory to move a file to, so in your case you do need separate calls.
You can use Rename-Item's -PassThru switch to make it output a System.IO.FileInfo instance (or, if a directory is being renamed, a System.IO.DirectoryInfo instance) representing the already renamed file; you can directly pass such an instance to Move-Item via the pipeline:
Get-ChildItem -File -Recurse -Path $sorDirectory -include '*.tif' |
ForEach-Object {
# ...
# Use -PassThru with Rename-Item to output a file-info object describing
# the already renamed file.
$renamedFile = $_ | Rename-Item -PassThru -NewName $_.Name.Replace("-", "").Replace("ABCALL", "20").Replace("CheckInvoiceFront", "CIF").Replace("CheckInvoiceBack", "CIB").Replace("CheckFront", "CF").Replace("CheckBack", "CB")
# ...
# Pass $renamedFile to Move-Item via the pipeline.
$renamedFile | Move-Item -Destination "$fullPath\$image"
# ...
}
As for your desire to:
make the Rename-Item return its results back to "$_"
While PowerShell doesn't prevent you from modifying the automatic $_ variable, it is better to treat automatic variables as read-only.
Therefore, a custom variable is used above to store the output from Rename-Item -PassThru
You need -passthru and -destination:
rename-item file1 file2 -PassThru | move-item -Destination dir1
I am working on inserting the space before every capitalized characters to rename the word documents in power shell
I tried to insert the space before first character for every file in this folder using the following script in power shell:
Could you pls let me know how to write a script to insert and iterate ?
#Target: the files names will appear as shown here: 220519ColdWaterMeters[enter image description here][1] , change all of the file names by adding a spaces in the file names so it will read as “220519 Cold Water Meters”
previous code
PS C:\Users\B> $source_path = "C:\Temp"
PS C:\Users\B> $filter = "*.doc"
PS C:\Users\B> $new_prefix = " "
PS C:\Users\B> $files = Get-ChildItem -Path $source_path -Filter $filter
PS C:\Users\B> # Process each file and add the $new_prefix to the filenames
>> ForEach ($file in $files) {
>>
>> $old_file_name = $file.Name
>> $new_full_name = "$($file.DirectoryName)" + "\" + "$($new_prefix)" + "$($old_file_name)"
>>
>> # Rename the file (perhaps first with the -WhatIf parameter?)
>> # Rename-Item $file.FullName -NewName $new_full_name -WhatIf
>> Rename-Item $file.FullName -NewName $new_full_name
>>
>> } # ForEach $file
Really, having spaces in filenames, folder names, filed names, property names, is a prescription for unneeded headaches in coding later. Doing so, can/will cause quoting complexities.
Yet, if you really want to do this, try this approach.
'220519ColdWaterMeters' -csplit '(?=[A-Z])' -ne '' -join ' '
# Results
<#
220519 Cold Water Meters
#>
You can use -creplace with delay-bind scriptblock in one pipeline like this.
$source_path = "C:\Temp"
$filter = "*.doc"
$new_prefix = " "
Get-ChildItem -Path $source_path -Filter $filter |
Rename-Item -NewName {($_.basename -creplace '(?=[A-Z])',"$new_prefix") + $_.Extension}
Just in case the extension had a capital letter I targeted just the basename and then added the extension back.
I have been looking around for a way to quickly and easily rename hundreds os files in one go.
something where I only have to change smalle parts for it to be reused somewhere else.
So i ended up starting to make this script. shown below...
the output should come out like this:
Show Title - SXX.EXXX - Episode title - [release year]
the raw files all looks like this:
XXX Episode title [release year]
It does not work right now. and i haven't been able to see why yet.
Whenever i run it, it does nothing. but i do not get any error message.
$ShowTitle = "My Title -"
$SeasonNumber = "02"
# Getting all child files (In ALL subfolders)
$files = Get-Childitem –Path Get-Location -Recurse |
Where-Object { $_.Name -match $_.Name } |
# Insert a ' - ' between the episode number and the episode text.
Rename-Item -NewName {$_.BaseName.insert(5,'-') + $_.Extension} |
# Append title and season number to the beginning of the file.
Rename-Item -NewName { $ShowTitle + "S" + $SeasonNumber + ".E" + $_.Name} |
# Makes a "-" between episode title and year of release.
Rename-Item -NewName { $_.Name -replace '\[', '- [' }
it worked on a smaller scale before. like this:
$files = Get-Childitem –Path "C:\Users\user\Videos\Series\show\Season x" -Recurse |
Where-Object { $_.Name -match 'show title' } |
Rename-Item -NewName { $_.Name -replace '\[', '- [' }
But i would like to do all the steps above in one go.
Can someone give me a hint so I can find the right answer to my little problem?
Thank you in advance.
You've got a lot of bugs here.
Get-Childitem –Path Get-Location -Recurse
This doesn't make sense. You're looking for a file or folder in the current directory with the literal name Get-Location. Like C:\Get-Location\. If you want to get the files in the current directory, you just don't specify the -Path parameter: Get-ChildItem -Recurse.
Where-Object { $_.Name -match $_.Name } is kind of nonsense code? The right hand side of the -match operator is going to be treated as a regular expression. That means . means "any character", square brackets and parentheses have special meaning, and so on. It's often going to always be true, but I can't imagine that you actually want to do what that says. It's very possible to construct a valid filename that doesn't match a regular expression with the same string value. For example '[01] File.avi' -match '[01] File.avi' is false.
Second, the -NewName parameter takes a string, while {$_.BaseName.insert(5,'-') + $_.Extension} is a ScriptBlock. That may work because some parts of Powershell allow that, but idiomatically I would say that it's wrong because it will not work consistently. A better option would be to use a string with embedded subexpressions like -NewName "$($_.BaseName.Insert(5,'-'))$($_.Extension)"
Finally, Rename-Item doesn't pass any output to the pipeline without the -PassThru parameter. You'd only process the first item and then I imagine the system would complain of an empty pipeline or only the first Rename-Item would do anything.
Try something like this:
$ShowTitle = "My Title -"
$SeasonNumber = "02"
# Getting all child files (In ALL subfolders)
$files = Get-Childitem -Recurse |
Where-Object { $_.Name -match 'some value or delete this command if you want all files' } |
# Insert a ' - ' between the episode number and the episode text.
Rename-Item -NewName "$($_.BaseName.Insert(5,'-'))$($_.Extension)" -PassThru |
# Append title and season number to the beginning of the file.
Rename-Item -NewName "$($ShowTitle)S$($SeasonNumber).E$($_.Name)" -PassThru |
# Makes a "-" between episode title and year of release.
Rename-Item -NewName "$($_.Name -replace '\[', '- [')" -PassThru
Thanks to #Theo yesterday I got my script to work and do exactly all the things I wanted. So now have a lovely menu system, options etc.
But I've decided - like a charm I wanted to do a little more!
So part of what is happening is finding files and renaming them. Theo's example was best:
D:\The Old Man and his dog.mp4 → D:\Old Man and his dog (The).mp4
D:\A Nightmare on Elm Street.mp4 → D:\Nightmare on Elm Street (A).mp4
D:\I, Robot.mkv → D:\Robot (I).mkv
What I wanted to do is change from output to screen the results. To a log file of changes made, and preferably update the file and add or make a new log file every time the script runs. Just in case I ever need to find something or check what happened - I've got something to refer back to!
Below is the code I've used but not to happy with to display the results.
Start-Sleep -s 5
cls
Write-Output "
Ok then, $filter and $rename has now been searched and changed.
With a total of
(Get-ChildItem | ? {$_.CreationTime -ge (Get-Date).Addminutes(-10)}).Count
Write-Output "files being changed.
"
pause
cls}
I would like to get a better display result, i.e.
xx Files have been successfully update
xx Files failed.
Please see log for details.
And then look in the log file and see something like:
Log1.txt:
D:\The Old Man and his dog.mp4 successfully Changed to D:\Old Man and his dog (The).mp4
Total of 1 /1 files successfully changed
I'm thinking of Compare-Object and Out-File, so maybe
Compare-Object $(Get-Content c:\user\documents\List1.txt) $(Get-Content c:\user\documents\List2.txt) > c:\user\documents\diff_output.txt
But for some reason I cannot get my head around this idea and figure out where to start.
If I understand the requirements correctly, extending on my previous code, you could do this:
$rootFolder = 'D:\' #'# the root folder where the files and subfolders are
$logFile = 'D:\renamed_{0:yyyy-MM-dd HH_mm_ss}.txt' -f (Get-Date) #'# the file to log to, including the current date
# create an array of words that start the file name and should be moved towards the back
$words = 'The', 'i', 'a'
# create a regex pattern where all possible words are separated by the regex OR (|) sign
$re = '^({0})[ ,]+' -f ($words -join '|')
# create two integer counter variables
[int]$success, [int]$failed = 0
Get-ChildItem -Path $rootFolder | Where-Object { $_.Name -match $re } | ForEach-Object {
$newName = '{0} ({1}){2}' -f ($_.BaseName -replace $re), $matches[1], $_.Extension
try {
$_ | Rename-Item -NewName $newName -ErrorAction Stop -WhatIf
Add-Content -Path $logFile -Value "Successfully renamed '$($_.FullName)' to '$newName'"
$success++
}
catch {
Add-Content -Path $logFile -Value "Rename failed for file '$($_.FullName)'"
$failed++
}
}
# write summary
$total = $success + $failed
$summary = "Total of $success / $total files successfully changed`r`nTotal of $failed / $total files failed"
# output summary to the log file
Add-Content -Path $logFile -Value "`r`n==========================================================="
Add-Content -Path $logFile -Value $summary
# output summary on screen
Write-Host "`r`n$summary`r`nPlease see '$logFile' for details." -ForegroundColor Yellow
Using the same three examples will output this on screen:
Total of 3 / 3 files successfully changed
Total of 0 / 3 files failed
Please see 'D:\renamed_2019-09-18 12_07_01.txt' for details.
and the log file will contain:
Successfully renamed 'D:\A Nightmare on Elm Street.mp4' to 'Nightmare on Elm Street (A).mp4'
Successfully renamed 'D:\I, Robot.mkv' to 'Robot (I).mkv'
Successfully renamed 'D:\The Old Man and his dog.mp4' to 'Old Man and his dog (The).mp4'
===========================================================
Total of 3 / 3 files successfully changed
Total of 0 / 3 files failed
I have the below script:
function copyUserSettings {
Write-Host
$copyFrom = Read-Host 'Which Folders Do You Want To Copy FROM?'
Write-Host
$copyTo = Read-Host 'Which Folders Do You Want To Copy TO? (Enter a Number OR Range eg. 12-18)'
Write-Host
IF ($copyTo.Contains("-")) {
$copyToStart = $copyTo.Split("-")[0]
$copyToEnd = $copyTo.Split("-")[1]
$copyToStart..$copyToEnd | foreach{
Copy-Item -Path $rootPath\FOLDER\$copyFrom\US*.DAT -Destination $rootPath\FOLDER\$_
}
} else {
Copy-Item -Path $rootPath\FOLDER\$copyFrom\US*.DAT -Destination $rootPath\FOLDER\$copyTo
}
}
The user is supposed to enter where to copy the files from (all the folder names are just a number), and where to copy the files to (also just a number), by entering a single folder name or a range (ie 12-18). If I enter a single number the above script works properly, but if I enter a range the files don't copy and I don't get any feedback error or anything.
Edit1: $rootPath is defined earlier in the script.
Edit2: Modified code above per #tnw's suggestion.
Thanks in advance for your help. If you need any more details please let me know.
It appears you've misplaced a closing bracket. I've moved that bracket to where it should be (I think). You should exercise better tabbing to avoid these issues:
function copyUserSettings {
Write-Host
$copyFrom = Read-Host 'Which Folders Do You Want To Copy FROM?'
Write-Host
$copyTo = Read-Host 'Which Folders Do You Want To Copy TO? (Enter a Number OR Range eg. 12-18)'
Write-Host
IF ($copyTo -Contains "-") {
$copyToStart = $copyTo.Split("-")[0]
$copyToEnd = $copyTo.Split("-")[1]
$copyToStart..$copyToEnd | foreach {
Copy-Item -Path $rootPath\FOLDER\$copyFrom\US*.DAT -Destination $rootPath\FOLDER\$_
} #this bracket was missing
} else {
Copy-Item -Path $rootPath\FOLDER\$copyFrom\US*.DAT -Destination $rootPath\FOLDER\$copyTo
}
} #you had an extra closing bracket here
EDIT: Figured it out. Look at this:
"12-18" -Contains "-"
Evaluates to false, so your if never evaluates to true. This is because -Contains is for collections, not substrings. From the documentation: "Tells whether a collection of reference values includes a single test value"
Try this instead:
IF ($copyTo.Contains("-"))
You can also use the -Match operator:
IF ($copyTo -Match "-")
The -contains operator checks if a set (e.g. a list/array) contains a particular item. To check if a string contains a particular character you need to use the -like or -match operator:
$copyTo -like '*-*'
$copyTo -match '-'
The -match operator would also allow you to verify if the string contains numbers in the right places and extract those numbers from the string:
if ($copyTo -match '^(\d+)-(\d+)$') {
$start = $matches[1]
$end = $matches[2]
}
Another option would be using the Contains() method of the string class as tnw suggested:
$copyTo.Contains('-')
If I understand your question correctly, $_ is supposed to be destination folder name. But your script doesn't know that so it treats $_ as file name. You need to create destination folders (if they don't exist) and change your Copy-Item destination. Example:
$copyToStart..$copyToEnd | foreach {
New-Item -Type Directory -Path $rootPath\FOLDER\$_
Copy-Item -Path $rootPath\FOLDER\$copyFrom\US*.DAT -Destination $rootPath\FOLDER\$_\US*.DAT
}