Powershell - Rename files preserving part of the name + checksum - powershell

I have some long filenames that potensially can cause issues with Windows path max chars, and I would like to rename them preserving part of it - and also adding a RNG 4 letter/digit combination.
Filename example: 478432_1400_79834_SomeKindofText_UserInputSoItCanBeReallyLongCombinedWithANetworkPath.jpg
Wanted rename outcome:
478432_1400_79834_SomeKindofText_abc1.jpg
Where 'abc1' represents the 4 letter/digit combination of a checksum
This is the code I have so far:
$search_folder = "C:\PS\Test\"
Get-ChildItem $search_folder -File | ForEach-Object {
$checksum = Get-FileHash -Path $_
$checksum = $checksum.substring(0,3)
Rename-Item -NewName { $search_folder+$_.BaseName.Split('_')[0..3] + $checksum + $_.Extension }
}
My first problem is that Get-FileHash does not support substring method, generating a error message:
Method invocation failed because [Microsoft.Powershell.Utility.FileHash] does not contain a method named 'substring'.
My second problem is that it tries to do a Resolve-Path in my current PS shell directory instead of $search_folder
My third problem is that the underscores in the filename is not preserved, so a -WhatIf tag on the Rename-Item method yields a result like "478432 1400 79834 SomeKindofText"
Tips or suggestions would be most welcomed!

My first problem is that Get-FileHash does not support substring method, generating a error message:
Method invocation failed because [Microsoft.Powershell.Utility.FileHash] does not contain a method named 'substring'.
$checksum does not store the hash string, it stores an object that has a property named Hash, which in turn stores the string you want, so change this line:
$checksum = $checksum.substring(0,3)
To:
$checksum = $checksum.Hash.Substring(0,3)
My second problem is that it tries to do a Resolve-Path in my current PS shell directory instead of $search_folder
Two general solutions to this problem:
Pass the absolute path to the file explicitly:
Rename-Item -LiteralPath $_.FullName -NewName { ... }
Or pipe the output from Get-ChildItem directly to Rename-Item and let PowerShell bind the path correctly:
$_ |Rename-Item -NewName { ... }
My third problem is that the underscores in the filename is not preserved, so a -WhatIf tag on the Rename-Item method yields a result like "478432 1400 79834 SomeKindofText"
Splitting a string on '_' will also remove the underscores - to reverse this, use '_' as a delimiter in a -join operation:
$firstFourPartsOfBaseName = $_.BaseName.Split('_')[0..3] -join '_'
Putting this all together, we get:
$search_folder = "C:\PS\Test\"
Get-ChildItem $search_folder -File | ForEach-Object {
$checksum = Get-FileHash -Path $_
$checksum = $checksum.hash.substring(0,3)
$_ |Rename-Item -NewName {
# calculate new base name ("478432_1400_79834_SomeKindofText_abc1")
$newBasename = #($_.BaseName.Split('_')[0..3]; $checksum) -join ''
# add extension and output
$newBasename,$_.Extension -join '.'
}
}

Please see your script adjusted below:
$search_folder = "C:\PS\Test\"
Get-ChildItem $search_folder -File | ForEach-Object {
$checksum = Get-FileHash -Path $_
$checksum = $checksum.hash.substring(0,3)
Rename-Item -Path $_ -NewName ($search_folder + $_.BaseName.Split('_')[0..3] + $checksum + $_.Extension)
}
You were trying to get a sub string of an object, using the property hash on $Checksum will allow you to create a substring.
I have also added -path to the rename-item request and changed the parenthesis on the string construction (it could be either of these that were causing you an issue, unfortunately I haven't tested this.)

Related

Script lists all files that don't contain needed content

I'm trying to find all files in a dir, modified within the last 4 hours, that contain a string. I can't have the output show files that don't contain needed content. How do I change this so it only lists the filename and content found that matches the string, but not files that don't have that string? This is run as a windows shell command. The dir has a growing list of hundreds of files, and currently output looks like this:
File1.txt
File2.txt
File3.txt
... long long list, with none containing the needed string
(powershell "Set-Location -Path "E:\SDKLogs\Logs"; Get-Item *.* | Foreach { $lastupdatetime=$_.LastWriteTime; $nowtime = get-date; if (($nowtime - $lastupdatetime).totalhours -le 4) {Select-String -Path $_.Name -Pattern "'Found = 60.'"| Write-Host "$_.Name Found = 60"; }}")
I tried changing the location of the Write-Host but it's still printing all files.
Update:
I'm currently working on this fix. Hopefully it's what people were alluding to in comments.
$updateTimeRange=(get-date).addhours(-4)
$fileNames = Get-ChildItem -Path "K:\NotFound" -Recurse -Include *.*
foreach ($file in $filenames)
{
#$content = Get-Content $_.FullName
Write-host "$($file.LastWriteTime)"
if($file.LastWriteTime -ge $($updateTimeRange))
{
#Write-Host $file.FullName
if(Select-String -Path $file.FullName -Pattern 'Thread = 60')
{
Write-Host $file.FullName
}
}
}
If I understood you correctly, you just want to display the file name and the matched content? If so, the following will work for you:
$date = (Get-Date).AddHours(-4)
Get-ChildItem -Path 'E:\SDKLogs\Logs' | Where-Object -FilterScript { $date -lt $_.LastWriteTime } |
Select-String -Pattern 'Found = 60.' |
ForEach-Object -Process {
'{0} {1}' -f $_.FileName, $_.Matches.Value
}
Get-Date doesn't need to be in a variable before your call but, it can become computationally expensive running a call to it again and again. Rather, just place it in a variable before your expression and call on the already created value of $date.
Typically, and for best practice, you always want to filter as far left as possible in your command. In this case we swap your if statement for a Where-Object to filter as the objects are passed down the pipeline. Luckily for us, Select-String returns the file name of a match found, and the matched content so we just reference it in our Foreach-Object loop; could also use a calculated property instead.
As for your quoting issues, you may have to double quote or escape the quotes within the PowerShell.exe call for it to run properly.
Edit: swapped the double quotes for single quotes so you can wrap the entire expression in just PowerShell.exe -Command "expression here" without the need of escaping; this works if you're pattern to find doesn't contain single quotes.

Bulk renaming files with different extensions in order using powershell

is there a way to bulk rename items such that a folder with the items arranged in order would have their name changed into numbers with zero padding regardless of extension?
for example, a folder with files named:
file1.jpg
file2.jpg
file3.jpg
file4.png
file5.png
file6.png
file7.png
file8.jpg
file9.jpg
file10.mp4
would end up like this:
01.jpg
02.jpg
03.jpg
04.png
05.png
06.png
07.png
08.jpg
09.jpg
10.mp4
i had a script i found somewhere that can rename files in alphabetical order. however, it seems to only accepts conventionally bulk renamed files (done by selecting all the files, and renaming them such that they read "file (1).jpg" etc), which messes up the ordering when dealing with differing file extensions. it also doesn't seem to rename files with variations in their file names. here is what the code looked like:
Get-ChildItem -Path C:\Directory -Filter file* | % {
$matched = $_.BaseName -match "\((?<number>\d+)\)"
if (-not $matched) {break;}
[int]$number = $Matches["number"]
Rename-Item -Path $_.FullName -NewName "$($number.ToString("000"))$($_.Extension)"
}
If your intent is to rename the files based on the ending digits of their BaseName you can use Get-ChildItem in combination with Where-Object for filtering them and then pipe this result to Rename-Item using a delay-bind script block.
Needles to say, this code does not handle file collision. If there is more than one file with the same ending digits and the same extension this will error out.
Get-ChildItem -Filter file* | Where-Object { $_.BaseName -match '\d+$' } |
Rename-Item -NewName {
$basename = '{0:00}' -f [int][regex]::Match($_.BaseName, '\d+$').Value
$basename + $_.Extension
}
To test the code you can use the following:
#'
file1.jpg
file2.jpg
file3.jpg
file4.png
file5.png
file6.png
file7.png
file8.jpg
file9.jpg
file10.mp4
'# -split '\r?\n' -as [System.IO.FileInfo[]] | ForEach-Object {
$basename = '{0:00}' -f [int][regex]::Match($_.BaseName, '\d+$').Value
$basename + $_.Extension
}
You could just use the number of files found in the folder to create the appropriate 'numbering' format for renaming them.
$files = (Get-ChildItem -Path 'D:\Test' -File) | Sort-Object Name
# depending on the number of files, create a formating template
# to get the number of leading zeros correct.
# example: 645 files would create this format: '{0:000}{1}'
$format = '{0:' + '0' * ($files.Count).ToString().Length + '}{1}'
# a counter for the index number
$index = 1
# now loop over the files and rename them
foreach ($file in $files) {
$file | Rename-Item -NewName ($format -f $index++, $file.Extension) -WhatIf
}
The -WhatIf switch is a safety measure. With this, no file gets actually renamed, you will only see in the console what WOULD happen. Once you are content with that, remove the -WhatIf switch from the code and run again to rename all your files in the folder

Bulk rename files in powershell and add a count to each filename

I am able to bulk rename files in a directory OR substitute each file name with a count, however I need both combined.
Bulk rename (keep first 2 charachters of original name):
Get-ChildItem * |
Rename-Item -NewName { $_.BaseName.Split('-')[0] + $_.Extension }
(partially hard coded solution, I know!)
Alternatively, add count:
$count = 1
Get-ChildItem * | % { Rename-Item $_ -NewName (‘{0}.xlsx’ -f $count++) }
(I am not even dreaming about trailing 0s)
I tried to combine both things, but to no avail. What am I doing wrong?
My failed attempt:
$count = 1
Get-ChildItem * |
Rename-Item -NewName { $_.BaseName.Split('-')[0] -f $count++ + $_.Extension }
You're misunderstanding how the format operator works. You need a format string with a placeholder ({0}) in order to make that operator work. I would also recommend putting grouping parentheses around that expression, even though that shouldn't be required in this case. Just to be on the safe side.
('foo {0} bar' -f $some_var) + $other_var
With that said, you apparently want to append the value of the counter variable to the fragment from the original file name rather than using that fragment as a format string. For that you can simply concatenate the counter to the fragment, just like you do with the extension.
For the counter to work correctly you also need to specify the correct scope. The way you're using it defines a new variable $counter in the local scope of the scriptblock every time a file is renamed, so the variable $counter in the parent scope is never incremented. Use the script: or global: scope modifier to get the variable you actually intend to use.
$count = 1
Get-ChildItem * |
Rename-Item -NewName { $_.BaseName.Split('-')[0] + $script:count++ + $_.Extension }
If you do want to use the format operator instead of string concatenation (+) you'd use it like this:
$count = 1
Get-ChildItem * |
Rename-Item -NewName { '{0}{1}{2}' -f $_.BaseName.Split('-')[0], $script:count++, $_.Extension }
To have the $count counted up, you have to go with the loop.
$count = 1
Get-ChildItem * | foreach-object { Rename-Item -NewName { $_.BaseName.Split('-')[0] -f
count++ + $_.Extension } }
% is the alias for the foreach-object cmdlet you used in the "add count" approach.
Just added it to you failed attempt.

Extract date from a path in powershell

I have a folder called files which has a path like : C:\users\xxxx\desktop\files
Inside this folder are different folders: 2015-12-02, 2015-12-01, 2015-11-30, etc
Inside each folder there are multiple files. I was looking to append the folder date at the end of each file inside the folder. I have written the below script for that:
function checkfile($file) {
$filenm = $file.FullName
return($filenm.Contains('.txt'))
}
function renamefile($file) {
$filenm = $file.Name
$ip = $file.FullName.Substring(34)
$ip1 = $ip.Substring(1,4) + $ip.Substring(6,2) + $ip.Substring(9,2)
$txt = $filenm.Split(".")[1] + "_" + $file.name.Split(".")[3] + "_" + $file.name.Split(".")[4] + "." + $file.name.Split(".")[2] + "." + $ip1
Rename-Item $file.FullName -NewName $txt
}
$sourcepath = "C:\users\xxxx\desktop\files"
$inputfiles = (Get-ChildItem -Path $sourcepath -Recurse) | Where-Object { checkfile $_ }
foreach ($inputfile in $inputfiles) {
renamefile $inputfiles
}
The problem I'm facing is in the above script I have used substring(34) to extract the date from the file path. If for some reason the source path changes (to say : H:\powershell\scripts\files) then 34 will not work.
How can I extract the correct date from the file path irrespective of the full file path?
Why not:
$sourcepath = "C:\users\xxxx\desktop\files"
Get-ChildItem -Path $sourcepath -Include "*.txt" -Recurse | % {
Rename-Item $_ "$($_.BaseName)_$($_.Directory)$($_.Extension)"
}
BaseName is the file name without the extension
Directory is the directory name (your date)
Extension is the file extension (i.e. .txt)
$(...) is used to make sure ... is evaluated properly
% is an alias for ForEach-Object and will iterate over the objects coming from the pipeline.
$_ will hold the current object in the ForEach loop
Here, your checkfile function is replaced by -Include "*.txt".
Example :
C:\users\xxxx\desktop\files\2015-12-02\sample.txt
becomes
C:\users\xxxx\desktop\files\2015-12-02\sample_2015-12-02.txt
Not sure if you need it, but if you want to remove the dashes from the date, you could use:
Rename-Item $_ "$($_.BaseName)_$($_.Directory -replace '-','')$($_.Extension)"
EDIT : OP wished to remove the dashes but append the date after the file extension, so:
$sourcepath = "C:\users\xxxx\desktop\files"
Get-ChildItem -Path $sourcepath -Include "*.txt" -Recurse | % {
Rename-Item $_ "$($_.Name).$($_.Directory.Name -replace '-', '')"
}
The particulars of the problem aren't entirely clear. I gather that the date your are interested in is in the fullpath. You want to extract the date from the path and rename the file, such that the new filename includes that date at the end.
However your script implies that there are at least five periods in the path. But I don't see that mentioned in the OP anywhere.
So there are a few problems and open items I see:
1. What is the syntax of a full path? That includes the five or more periods
2. Will the date always be at the same directory depth? I'm guessing xxxx represents the date. If so the date is the second subdirectory. Will the date always be in the second subdirectory?
3. Related to #2, will there ever be paths that include two or more dates?
Assuming my guesses are correct AND that the date will always be the second subdirectory, then extracting the date would be:
`$dateString = $file.fullpath.split('\')[3]
If some of my guesses are incorrect then please add details to the OP. If #3 is true then you'll need to also explain how to know which date is the correct date to use.
An option that you could do is just cd to each path. Then use Get-ChildItem in each dir, without using -Recurse.
In rename $ip would just be $file, no need for FullName.
For example, define your functions and then:
$sourcefile = 'Whateversourcediris'
cd $sourcefile
$directories = (Get-ChildItem)
foreach ($direct in $directories) {
cd $direct
$inputfiles = (Get-ChildItem)|where-object{checkfile $_}
foreach ($inputfile in $inputfiles) {
renamefile $inputfile
}
cd..
}
Hope this helps.

How do I consolidate copy and rename commands into one using Powershell?

Currently I am using PS to copy files from a network location based on a CSV file, then I am renaming them using a variation of the same data. This requires that I run two separate commands.
How do I consolidate these commands into one?
Copy:
import-csv C:\TEST\test.csv | foreach {copy-item -path $_.npath -destination 'C:\TEST\'}
Paste:
import-csv C:\TEST\test.csv | foreach {rename-item -path $_.lpath -newname $_.newalias}
Notice that the -path trigger in each case refers to a separate variable header, npath vs. lpath which correspond to the network file location, and then a local file location which have been manually entered.
On the same note, how could I concatenate this variable to constant data. If I have a variable fn which represents the file name and another path, could I theoretically do:
foreach {rename-item -path 'C:\TEST\' + $_.fn
Or:
foreach {rename-item -path $_.path + $_.fn
Just append the two commands
import-csv C:\TEST\test.csv | foreach {copy-item -path $_.npath -destination 'C:\TEST\';rename-item -path $_.lpath -newname $_.newalias }
for your second question there are lots of ways to append string
C:(...)WindowsPowerShell>$data = "bob"
C:(...)WindowsPowerShell>echo "this is a $data"
C:(...)WindowsPowerShell>$concat = "hi" + " george"
C:(...)WindowsPowerShell>$concat
hi george
C:(...)WindowsPowerShell>[string]::Format("{0} {1}","string 1","string 2")
string 1 string 2