Rename a list of files - powershell

I need to rename all files below pwd named all_v4_0.csv to all_v4_1.csv.
So far, I have worked my way to this piece of PowerShell:
$oldfiles = dir -recurse | ?{$_.Name -eq "all_v4_0.csv"}
foreach ($o in $oldfiles) {
$o.CopyTo Join-Path $o.Directory.ToString() "all_v4_1.csv"
}
But the foreach loop fails with the message that
At line:2 char:15
+ $o.CopyTo Join-Path $o.Directory.ToString() "all_v4_1.csv"
+ ~~~~~~~~~
Unexpected token 'Join-Path' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
What am I doing wrong here?
Update, 20150604
As commented below by Manuel Batsching, the original version can be fixed by adding two layers of parentheses: one to indicate function argument, and one to force evaluation order:
$oldfiles = dir -recurse | ?{$_.Name -eq "all_v4_0.csv"}
foreach ($o in $oldfiles) {
$o.CopyTo((Join-Path $o.Directory.ToString() "all_v4_1.csv"))
}
For the problem at hand, one of the solutions with .FullName.Replace would probably be easier.

PSH's parser is not reading the Join-Path and its arguments as an expression to evaluate and pass result to outer expression. So parentheses to force evaluation order.
But additionally CopyTo is a .NET member, rather than a PSH cmdlet, so it needs to have parentheses around its argument (and no space).
Thus:
$o.CopyTo((Join-Path $o.Directory.ToString() "all_v4_1.csv"))
(Possibly using PSH's Copy-Item cmdlet would be a cleaner option.)

Set the target name separately before doing the copy. The below should work:
$oldfiles = dir -recurse | ?{$_.Name -eq "all_v4_0.csv"}
foreach ($o in $oldfiles) {
$newName = $o.FullName.replace("all_v4_0.csv","all_v4_1.csv")
Copy-Item $o.FullName $newName
}

If you want to keep things simple you can also use string concatenation to create the target path.
Get-ChildItem -Recurse -Include 'all_v4_0.csv' |
ForEach { $_.MoveTo($_.Directory.FullName + '\all_v4_1.csv') }

Simply replace the substring in the new name of the files:
Get-ChildItem -Recurse -Include 'all_v4_0.csv' |
Rename-Item -NewName { $_.Name.Replace('4_0', '4_1') }

Related

Feeding Get-ChildItem path info from an array - Illegal characters

I need to iterate over an array of folder names, passing them into GCI so I can run operations on their contents. No matter what I do, it keeps giving me this error:
gci : Illegal characters in path.
At C:\Scripts\Arvest_submission_monitoring.ps1:86 char:5
+ gci -path "D:\subftp\$_" -recurse {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (D:\SUBFTP\folder1:String [Get-ChildItem], ArgumentException
+ FullyQualifiedErrorId :
DirArgumentError,Microsoft.PowerShell.Commands.GetChildItemCommand
I've created an array:
$folders = "folder1","folder2","folder3"
And then iterate over it:
$folders | % {
gci -Path "D:\subftp\$_" -Recurse {
#do stuff here
}
}
I've tried many various options such as a regex replace to remove any possible illegal characters (although a $folder.Count shows me there are no invisible characters), I've tried turning $folders into a C# ArrayList but it's still a no go. I've even played around with the path variable itself concatenating it into a single string before use.
The weird thing is if I try and execute it from the command line it works fine. What gives? I'm running v5.
It is caused by the curly brace right at the end of your command gci -Path "D:\subftp\$_" -Recurse {.
Probably you wanted to do this:
$folders | ForEach-Object {
Get-ChildItem -Path "D:\subftp\$_" -Recurse | ForEach-Object {
#do stuff here
}
}

How does one pass vars into nested loops in PowerShell?

I am attempting to write a small PowerShell script to clean up files names from some log dumps, but I seem to be stuck... I have logs dumped from various sources, and the file names seem to be getting garbled up.
I am looking to for name the names of files like so... " Source - Service.log "
Get-ChildItem *.* -Path ~/Desktop/New | ForEach-Object {
while ([string]($_.Name) -notmatch "^[a-z].*" -or [string]($_.Name) -notmatch "^[A-Z].*") {
Rename-Item -NewName { [string]($_.Name).Substring(1) }
}
Write-Host $_.Name
}
The output seems to be erroring out.
Rename-Item : Cannot evaluate parameter 'NewName' because its argument is
specified as a script block and there is no input. A script block cannot be
evaluated without input.
At line:8 char:30
+ Rename-Item -NewName { $File.Substring(1) }
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [Rename-Item], ParameterBindingException
+ FullyQualifiedErrorId : ScriptBlockArgumentNoInput,Microsoft.PowerShell.Commands.RenameItemCommand
The Idea is to check the filename to to see if it is a character, and if not remove it, to remove ". - / and whitespace"
The orginal files I am running against are like this:
1. source - data (1).log
100. - source - Data.log
(1) Source - data.log
source - data.log
<space><space> source - data.log
and the result I am looking for from the above is: I am not concerned about the duplicates file names as source and data change day to day and the folder is cleared regularly...
source - data (1).log
source - Data.log
Source - data.log
source - data.log
source - data.log
Can someone tell me how to get past this error?
If your goal is to delete leading non-alpha characters, you can simplify what you're doing:
$files = Get-ChildItem -Path ~\Desktop\New -File
foreach ($file in $files)
{
if ($file.BaseName -notmatch '\S+\s-')
{
$newName = $file.Name -replace '^.+?(?=[a-z])'
$newName = Join-Path $file.DirectoryName $newName
if (Test-Path -Path $newName)
{
Remove-Item -Path $newName
}
$file | Rename-Item -NewName $newName
Write-Verbose $newName
}
}
This will iterate your list and look for your pattern, renaming where necessary. Assumption: the source doesn't have spaces.
This might be helpful: Remove-NonAlphanumericCharFromString.
Knowing how to remove non-alphanumeric, take the base name of the file (name without Path and extension).
Replace unwanted Chars with empty string.
$pattern = '[^a-zA-Z]'
Set-Location <YourDir>
Get-Childitem | Foreach-Object {
Rename-Item -Path ".\$($_.Name)" -NewName "$($_.BaseName -replace $pattern,'')$($_.extension)"
}
Note that above will fail upon a need to overwrite existing file.

How to Recursive Directory Loop, Match a Filename and Manipulate the File

I'm a Powershell novice so apologies for any wrong terminology. I've had a look on SE and I know how to recursively descend into a directory structure, however all the working examples I can find seem to pipe the output of Get-ChildItem, which I can't understand how to further work with the file before the loop iterates.
What I'm trying to do
Provide my script with a directory: C:\Users\Donglecow\myPictures\.
Recursively loop through all directories in the defined directory (I have an extensive file structure).
Find all files which match a filter thumbnail*.jpg, EG: thumbnail_001.jpg
Rename the file to BACKUP-thumbnail*.jpg
Check the dimensions of that image in pixels and print them out to the command line
What I've got so far
param (
[string]$dir = $pwd
)
write-output "Working from $($dir)"
$foundJpg = get-childitem -Recurse -Filter thumbnail*.jpg $dir
foreach($file in $foundJpg) {
write-output "Found $($file)"
#Rename to Backup...
#Check image dimensions...
#Do other stuff...
}
This works and outputs the correct file name, but when I try to rename the file I get an error.
Rename-Item -NewName { $_.Name.replace("thumbnail", "SAFE-thumbnail.jpg") }
Rename-Item : Cannot evaluate parameter 'NewName' because its argument is specified as a script block and there is no
input. A script block cannot be evaluated without input.
At C:\Users\Donglecow\myPictures\med.ps1:9 char:23
+ ... -Item -NewName { $_.Name.replace("thumbnail", "SAFE-thumbnail.jpg") }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [Rename-Item], ParameterBindingException
+ FullyQualifiedErrorId : ScriptBlockArgumentNoInput,Microsoft.PowerShell.Commands.RenameItemCommand
I've deducted this is probably because I've tried to adapt from a command that pipes the output as shown in answers on this question:
Get-ChildItem -Recurse -Filter *.mp3 | Rename-Item –NewName { $_.name –replace 'xyz','abc' }
Whereas I'm using a different construct for my loop so that I can further interact with each file that I find.
My environment
For reference, a snippet of my file structure is below.
C:\Users\Donglecow\myPictures\
1\001\thumbnail_001.jpg
1\002\thumbnail_001.jpg
1\003\thumbnail_001.jpg
2\001\thumbnail_001.jpg
etc...
Where am I going wrong here?
Try this:
foreach($file in $foundJpg) {
$File | Rename-Item -NewName { $_.Name.replace("thumbnail", "SAFE-thumbnail.jpg") }
#Do other stuff...
}
You need to use $file as the input of the command (which you should be able to do via the pipeline as shown above) as this is what your ForEach loop is using to represent each item retrieved by Get-ChildItem into the $foundJpg variable.

Search for a string in a number of files, and then rename the files with that string

I'm trying to create a PowerShell script that will search through a series of .txt files (.hl7 files to be exact, but they are just txt files) and search within those files to see if they contain a four digit number. If the file does contain that four digit number, it should then rename the file with the string added to the front of the original file name. So test.hl7 should become 8000_test.hl7 if that file includes those 4 digits within it.
After a day of ferocious googling and digging through this website, this is the best I could muster:
$AccountIDs = ("8155", "8156", "8428")
$Path = "C:\Users\ThatsMe\Downloads\messages"
$Files = (Get-ChildItem $Path -Filter "*.hl7")
for ($i = 0; $i -le $Files.Length; $i++) {
if (Get-Content $Files[$i].FullName | Select-String -Pattern $AccountIDs[$i]) {
Rename-Item $Files[$i].FullName -NewName (($AccountIDs[$i]) + "_" + $Files[$i].Name)
}
}
I am getting some interesting results. I currently have four test files in that messages folder, test, test2, test3, and skibbidybop. The very first one, test gets correctly changed to 8156_test. However, the other files aren't touched. Now, when I change the filename of test to ttest, the script completely skips over that file and then renames test2 and test3 to 8156_test2 (which is incorrect) and 8428_test3 respectively. skibbidybop is never touched.
And, of course, the error message from PowerShell:
Select-String : Cannot bind argument to parameter 'Pattern' because it is null.
At line:6 char:61
+ if (Get-Content $Files[$i].FullName | Select-String -Pattern <<<< $AccountIDs[$i]) {
+ CategoryInfo : InvalidData: (:) [Select-String], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SelectStringCommand
Get-Content : Cannot bind argument to parameter 'Path' because it is null.
At line:6 char:16
+ if (Get-Content <<<< $Files[$i].FullName | Select-String -Pattern $AccountIDs[$i]) {
+ CategoryInfo : InvalidData: (:) [Get-Content], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.GetContentCommand
Updated Code
$Path = "C:\Users\ThatsMe\Downloads\messages"
$pattern = '\b(8155|8156|8428)\b'
Get-ChildItem $Path -Filter '*.hl7' |
Select-String -Pattern $pattern |
Group-Object Path |
ForEach-Object {
$id = $_.Group.Matches[0].Groups[0].Value
$filename = $_.Group.Filename | Select-Object -First 1
Rename-Item -Path $_.Name -NewName "${id}_${filename}" -WhatIf
}
This is the error that I receive now:
C:\> C:\Users\ThatsMe\Downloads\messages\changename.ps1
Cannot index into a null array.
At C:\Users\ThatsMe\Downloads\messages\changename.ps1:8 char:38
+ $id = $_.Group.Matches[ <<<< 0].Groups[0].Value
+ CategoryInfo : InvalidOperation: (0:Int32) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
What if: Performing operation "Rename File" on Target "Item:
C:\Users\ThatsMe\Downloads\messages\test.hl7 Destination:
C:\Users\ThatsMe\Downloads\messages\_".
Cannot index into a null array.
At C:\Users\ThatsMe\Downloads\messages\changename.ps1:8 char:38
+ $id = $_.Group.Matches[ <<<< 0].Groups[0].Value
+ CategoryInfo : InvalidOperation: (0:Int32) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
What if: Performing operation "Rename File" on Target "Item:
C:\Users\ThatsMe\Downloads\messages\test3.hl7 Destination:
C:\Users\ThatsMe\Downloads\messages\_".
The errors you get are caused by two mistakes, one of them a classic off-by-one error. PowerShell arrays are zero-based, meaning that the last index of the array is one less than the number of its elements:
[ 'a', 'b', 'c' ] → count == 3
0 1 2 → last index == 2 == 3-1
Thus your for loop may run while $i is less than $Files.Length (-lt), not less or equal (-le):
for ($i = 0; $i -lt $Files.Length; $i++) {
Also, you cannot use the same index variable for two different arrays ($Files and $AccountIDs) unless you made sure both arrays have the same length or at least that the second array ($AccountIDs) has more elements than the one used to determine the maximum index ($Files). If $AccountIDs has less elements than $Files your code will eventually attempt to access an index beyond the upper boundary of $AccountIDs. Besides, you probably want to check each file for all of the numbers from $AccountIDs anyway. Doing that requires a nested loop with a second index variable.
With that said, you're making this more complicated than it needs to be. You can simply put your IDs in a single regular expression and pipe the list of files into Select-String to check them against that regular expression:
$pattern = '\b(8155|8156|8428)\b'
Get-ChildItem $Path -Filter '*.hl7' |
Select-String -Pattern $pattern |
Group-Object Path |
ForEach-Object {
$id = $_.Group.Matches[0].Groups[0].Value
$filename = $_.Group.Filename | Select-Object -First 1
Rename-Item -Path $_.Name -NewName "${id}_${filename}" -WhatIf
}
The regular expression \b(8155|8156|8428)\b matches any of the given numbers. The \b restrict the match to word boundaries to avoid matching numbers like 81552 or 842893 as well.
The Group-Object statement ensures the uniqueness of the renamed files (so that you don't attempt to rename a file more than once if more than one match is found in it).
.Matches[0].Groups[0].Value extracts the value of the first capturing group of the first match for each file.
The Select-Object -First 1 ensures that even if multiple matches were found in a file you have just one string with the filename, not an array of them.
Remove the -WhatIf switch once you verified that the rename operation would work correctly and re-run the whole statement to actually rename the files.
Edit: For PowerShell v2 you need to adjust the group handling a little bit, because that version doesn't support member enumeration.
Get-ChildItem $Path -Filter '*.hl7' |
Select-String -Pattern $pattern |
Group-Object Path |
ForEach-Object {
$id = $_.Group | Select-Object -Expand Matches -First 1 |
ForEach-Object { $_.Groups[0].Value }
$filename = $_.Group | Select-Object -Expand Filename -First 1
Rename-Item -Path $_.Name -NewName "${id}_${filename}" -WhatIf
}

PowerShell: I need to understand why parameters are interpreted as NULL

I'm getting an error that I can't call a method on a null valued expression. However, I'm not sure WHY the parameters are resulting in a null value. I need a second set of eyes to look at this and give me some guidance.
$docpath = "c:\users\x\desktop\do"
$htmPath = "c:\users\x\desktop\ht"
$txtPath = "c:\users\x\desktop\tx"
$srcPath = "c:\users\x\desktop\ht"
#
$srcfilesTXT = Get-ChildItem $txtPath -filter "*.htm*"
$srcfilesDOC = Get-ChildItem $docPath -filter "*.htm*"
$srcfilesHTM = Get-ChildItem $htmPath -filter "*.htm*"
#
function rename-documents ($docs) {
Move-Item -txtPath $_.FullName $_.Name.Replace("\.htm", ".txt")
Move-Item -docpath $_.FullName $_.Name.Replace("\.htm", ".doc")
}
ForEach ($doc in $srcpath) {
Write-Host "Renaming :" $doc.FullName
rename-documents -docs $doc.FullName
$doc = $null
}
And the error....
You cannot call a method on a null-valued expression.
At C:\users\x\desktop\foo002.ps1:62 char:51
+ Move-Item -txtPath $_.FullName $_.FullName.Replace <<<< ("\.htm", ".txt")
+ CategoryInfo : InvalidOperation: (Replace:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\users\x46332\desktop\foo002.ps1:63 char:51
+ Move-Item -docpath $_.FullName $_.FullName.Replace <<<< ("\.htm", ".doc")
+ CategoryInfo : InvalidOperation: (Replace:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
First: it appears that my ("\.htm", ".txt") is what's showing up as null. I've tried it without the \ - (".htm", ".txt") - as well and received the same results.
Second: syntactically, I'm interpreting my line as move-item <path> <source-file-passed-to-function> <replacement=name-for-file> (parameters-for-replacement). Is that an appropriate understanding of what this code is doing?
Third: Do I need to have a -literalpath parameter in there somewhere? MS TechNet and get-help have very little information on the uses of the -literalpath parameter; I was unable to find something relevant to my particular situation.
Help me understand what I'm missing. Thanks!
In the context of a simple function $_ is not defined. $_ is only valid in a pipeline. That is, $_ reprensents the current object being passed down the pipeline.
With your current function definition try it this way:
function Rename-HtmlDocument([System.IO.FileInfo]$docs, $newExt) {
$docs | Move-Item -Dest {$_.FullName -replace '\.htm$', $newExt}
}
You can pass this function the $srcfilesDOC and $srcFilesTXT variables directly e.g.:
Rename-HtmlDocument $srcFilesDOC .doc
Rename-HtmlDocument $srcFilesTXT .txt
Of course you could make this more generic and get the source extension from the FileInfo object e.g.:
function Rename-DocumentExtension([System.IO.FileInfo]$docs, $newExt) {
$docs | Move-Item -Dest {$_.FullName.Replace($_.Extension, $newExt)}
}
BTW PowerShell's Move-Item command doesn't have the parameters you're using -txtPath and -docPath. Is this a function you've created?