Renaming files with powershell (long and not standard name) - powershell

I'm just getting to know PowerShell.
I have a script (got it from an old administrator who quit), it worked well with files, renamed it as needed.
Old file name for example:
mcgruz 16.11.2021 03_30_1720211116_033432.xls
was renamed to
20211116_mcgruz
or another:
prim43 15.11.2021 23_00_1920211115_320117.xls
was renamed to
20211115_prim43
Script:
# Definition
$srcFld = "\\domain\MC_REPORT\INCOMING"
$arcFld = "\\domain\MC_REPORT\ARC"
$UnsortFld = "\\domain\MC_REPORT\UNSORT"
$destFld = $UnsortFld
$mcgruzFld = "\\domain\MC_REPORT\RAIL_RUN_DATE"
$mcdatcFld = "\\domain\MC_REPORT\DEMURRAGE_AT_THE_CUSTOMS"
$demurrFld = "\\domain\MC_REPORT\DEMURRAGE"
$prim43Fld = "\\domain\MC_REPORT\FLAT_CAR_PRICE_GROUP"
$podsylFld = "\\domain\MC_REPORT\PODSYL"
$pogruzFld = "\\domain\MC_REPORT\POGRUZKA"
$claimsFld = "\\domain\MC_REPORT\CLAIMS"
$dislzvFld = "\\domain\MC_REPORT\DISLZV"
$renummFld = "\\domain\MC_REPORT\RENUM"
$kontdislFld = "\\domain\MC_REPORT\KONTDISL"
$regex = "(\b\w+\b).(\d+[.]\d+[.]\d+)"
#
$folder = Get-ChildItem $srcFld -Filter *.xls
foreach($file in $folder) {
$fn=($file.Name -split $regex)
$fn[2] = date $fn[2] -format yyyyMMdd
$filenew = $fn[2]+"_"+$fn[1]+".xls"
switch ($fn[1])
{
renumm {
$destFld = $renummFld
}
mcgruz {
$destFld = $mcgruzFld
}
mcdatc {
$destFld = $mcdatcFld
}
prim43 {
$destFld = $prim43Fld
}
demurr {
$destFld = $demurrFld
}
podsyl {
$destFld = $podsylFld
}
pogruz {
$destFld = $pogruzFld
}
dislzv {
$destFld = $dislzvFld
}
claims {
$destFld = $claimsFld
}
kontdisl {
$destFld = $kontdislFld
}
default {
$destFld = $UnsortFld
}
}
Copy-Item $file.FullName "$destFld\$filenew" -Recurse
Copy-Item $file.FullName "$arcFld\$filenew" -Recurse
Remove-Item $file.FullName -recurse
}
But recently, the vendor changed the files, and now the script does not work correctly.
New file name: prim4320211205_230050.xls script renamed it to: _.xls and not safe old file.
I must to change the code, but I don’t know exactly what to change.
$regex = "(\b\w+\b).(\d+[.]\d+[.]\d+)"
#
$folder = Get-ChildItem $srcFld -Filter *.xls
foreach($file in $folder) {
$fn = ($file.Name -split $regex)
$fn[2] = date $fn[2] -format yyyyMMdd
$filenew = $fn[2]+"_"+$fn[1]+".xls"
I will be grateful for your help
try to use code on local machine. And powershell ISE send error:
+ $fn[2] = date $fn[2] -format yyyyMMdd
+ ~~~~~~
+ CategoryInfo : WriteError: (:) [Get-Date],
ParameterBindingException
+ FullyQualifiedErrorId :
ParameterBindingFailed,Microsoft.PowerShell.Commands.GetDateCommand

To handle both file naming formats, you could use below:
foreach ($file in (Get-ChildItem -Path $srcFld -Filter '*.xls' -File)) {
if ($file.BaseName -match '^(.+)(\d{8})_.*') {
# testing new format like 'prim4320211205_230050'
$name = $matches[1]
$filenew = '{0}_{1}{2}' -f $matches[2], $matches[1], $file.Extension
}
elseif ($file.BaseName -match '^(\w+) +(\d{1,2}\.\d{1,2}\.\d{4}) .*') {
# testing old format like 'mcgruz 16.11.2021 03_30_1720211116_033432'
$name = $matches[1]
$date = [datetime]::ParseExact($matches[2], 'dd.MM.yyyy', $null)
$filenew = '{0:yyyyMMdd}_{1}{2}' -f $date, $matches[1], $file.Extension
}
else {
# output a warning thatthe filename could not be parsed out
Write-Warning "could not rename file '$($file.FullName)'"
continue # skip this file and proceed with the next one
}
# now proceed with the rest of your code
$destFld = switch ($name) {
'renumm' { $renummFld }
'mcgruz' { $mcgruzFld }
# etc.
}
$file | Copy-Item -Destination (Join-Path -Path $destFld -ChildPath $filenew)
$file | Copy-Item -Destination (Join-Path -Path $arcFld -ChildPath $filenew)
$file | Remove-Item -Force
}

Regex expression explained on regex101.com, with test strings: regex101: build, test, and debug regex
(\b\w+\b).(\d+[.]\d+[.]\d+)
1st Capturing Group (\b\w+\b)
\b assert position at a word boundary: (^\w|\w$|\W\w|\w\W)
\w matches any word character (equivalent to [a-zA-Z0-9_])
+ matches the previous token between one and unlimited times, as many times as possible, giving back as needed (greedy)
\b assert position at a word boundary: (^\w|\w$|\W\w|\w\W)
. matches any character (except for line terminators)
2nd Capturing Group (\d+[.]\d+[.]\d+)
\d matches a digit (equivalent to [0-9])
+ matches the previous token between one and unlimited times, as many times as possible, giving back as needed (greedy)
Match a single character present in the list below [.]
. matches the character . with index 4610 (2E16 or 568) literally (case sensitive)
\d matches a digit (equivalent to [0-9])
+ matches the previous token between one and unlimited times, as many times as possible, giving back as needed (greedy)
Match a single character present in the list below [.]
. matches the character . with index 4610 (2E16 or 568) literally (case sensitive)
\d matches a digit (equivalent to [0-9])
+ matches the previous token between one and unlimited times, as many times as possible, giving back as needed (greedy)

Related

Powershell replace from array strange behaviour

Problem is solved, but I don't understand why :-)
I have a Powershell script that perform replacements inside files (language metadata):
loads a list of replacement from a txt file into an array
gets all xml files from a Start folder
performs all the replacements from the array
performs a replacement on the filename based on the array first entry
saves the resulting files in a End folder
I've been using successfully variations of the exact same script for many years, with the only thing changing being the replacement file name and content... except today when creating another variant. The only change was the content of the substitution file, and suddenly the replacement did not happen anymore in the filename.
Here is the code:
#load the replacements from file
$data = Import-Csv -Path substitutions.txt -Header "Source", "Target", "Safe", "Count" -Delimiter "|"
#load the files to be processed
$xmlfiles = -join ($Startfolder, "*.xml")
$Fileset = Get-ChildItem $xmlfiles -recurse
foreach ($File in $Fileset) {
$NewFileName = ""
$WipFile = Get-Content $File
# set safe replacement flag to nothing
$flag = ""
#perform replacements
foreach ($item in $data) {
if ($WipFile -cmatch $item.Source) {
if ($item.Safe -eq 'yes') {
$WipFile = $WipFile -creplace $item.Source, $item.Target
$item.Count = $item.Count + 1
}
else {
$WipFile = $WipFile -creplace $item.Source, $item.Target
$item.Count = $item.Count + 1
$flag = "TOCHECK "
}
}
}
#replace language code in filename, based on first entry in the substitution list
$NewFileName = -join ($Endfolder, $flag, $file.name -creplace $data.Source[0], $data.Target[0])
Write-Host $NewFileName
#save file with updated content
$WipFile | Set-Content -Encoding Unicode ($File)
#move file to End folder
Move-Item $File $NewFileName
}
The substitution file is formatted as follows:
nl-NL|nl-BE|yes
After testing more, I discovered my new variant was failing if my substitution file had only one line. Add another one, and it works. How come?

Bulk renaming photos and adding letters to duplicate file names in Powershell

I have a question about a powershell script. I want to rename a bunch of photos within a folder. I have a .csv file of the old names and the new names. This is a section of that file:
OldFile NewFile
{5858AA5A-DB1B-475A-808E-0BFF0B885E5B}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Notes-20200828.jpeg
{FA1E4CEE-0AD8-4B40-A5AD-4BB22C0EE4F0}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Other-20200828.jpeg
{FD20FA44-B3D2-4A6A-B73D-F3BADC2DDE71}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{E0DDA4CD-7783-417C-9BE0-705FFA08CD17}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{76DC6315-942D-444C-BA04-92FC9B9FF1A5}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{3C853453-0A0D-40B5-B3B7-B0F84F92D512}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
Many of the new file names will be duplicates. For those files, I want to add a letter (A,B,C, so on) in the middle of the name at an exact location.
For example, if the file, 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg, is a duplicate, I want to add "A" right after "Vicinity", so that the file is called 975NNNN-AGUIRRESUGARASSOCSTACK-VicinityA-20200831.jpeg. The letter will always be at that exact same location (right before the third -).
This is the script I have so far. I know it's not right and I haven't been able to even attempt at adding the letter within the script. (I'm a complete Powershell newbie.)
$filesToRename = Import-CSV C:\Users\clair\OneDrive\Documents\JOA\batch_photos\Rename_Central_Aguirre.csv
foreach ($file In $filesToRename) {
if (Test-Path $file.NewFile) {
$letter = -begin { $count= 1 } -Process { Rename-Item $file.OldFile
"file-$([char](96 + $count)).jpeg"; $count++}
} else {
Rename-Item $file.OldFile $file.NewFile
}
}
Could I get some guidance on how to achieve this file naming system?
Thanks!!!
When renaming files using a character from the alphabet will mean you will only have 26 options. If that is enough for you, you can do the following:
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
$folderPath = 'D:\Test'
$filesToRename = Import-CSV C:\Users\clair\OneDrive\Documents\JOA\batch_photos\Rename_Central_Aguirre.csv
foreach ($file In $filesToRename) {
$oldFile = Join-Path -Path $folderPath -ChildPath $file.OldFile
if (Test-Path $oldFile -PathType Leaf) {
# split the new filename into workable parts
$newName = $file.NewFile
$extension = [System.IO.Path]::GetExtension($newName)
$parts = [System.IO.Path]::GetFileNameWithoutExtension($newName) -split '-'
$suffix = $parts[-1]
$prefix = $parts[0..($parts.Count -2)] -join '-'
$charToAppend = 0 # counter to go through the characters in the alphabet. 0..25
while (Test-Path (Join-Path -Path $folderPath -ChildPath $newName) -PathType Leaf) {
if ($charToAppend -gt 25) {
# bail out if al characters have been used up
throw "Cannot rename file '$($file.OldFile)', because all characters A-Z are already used"
}
$newName = '{0}{1}-{2}{3}' -f $prefix, $alphabet[$charToAppend++], $suffix, $extension
}
Rename-Item -Path $oldFile -NewName $newName
}
else {
Write-Warning "File '$($file.OldFile)' not found"
}
}
Before:
D:\TEST
{3C853453-0A0D-40B5-B3B7-B0F84F92D512}.jpeg
{5858AA5A-DB1B-475A-808E-0BFF0B885E5B}.jpeg
{76DC6315-942D-444C-BA04-92FC9B9FF1A5}.jpeg
{E0DDA4CD-7783-417C-9BE0-705FFA08CD17}.jpeg
{FA1E4CEE-0AD8-4B40-A5AD-4BB22C0EE4F0}.jpeg
{FD20FA44-B3D2-4A6A-B73D-F3BADC2DDE71}.jpeg
After:
D:\TEST
975NNNN-AGUIRRESUGARASSOCSTACK-Notes-20200828.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-Other-20200828.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityA-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityB-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityC-20200831.jpeg
I think you need to use the method.Insert(). This is a small example how it works:
I ve created a txt named 975NNNN-AGUIRRERM1-Vicinity-20200829.txt in C:\Test just for testing purpose, in your example the first code line is to identify the duplicate(s)
#Code to identify duplicates (insert your code instead of mine)
$files=Get-ChildItem -Path C:\Test -File -Name
#The following line indentifies the location of the last "-" (I understand you always have 3 "-" right?)
$DashPos=($files).LastIndexOf("-")
#This inserts on the position $DashPos, the letter "A")
$files.Insert($DashPos,"A")

How can I find out the exact location where the recursive operation is working?

My problem is, that the string for replacement needs to change according to the folder depth the designated file is located and I don't have a clue how to get that info. I need to work with relative addresses.
I want the script to be run from 2 folder levels above the folder where all the files are that need correcting. So I've set the $path in line 1. That folder suppose to be 'depth 0'. In here, the replacement string needs to be in it's native form -> stylesheet.css.
For files in the folders one level below 'depth 0' the string for replacement needs to be prefixed with ../ once -> ../stylesheet.css.
For files in the folders two level below 'depth 0' the string for replacement needs to be prefixed with ../ twice -> ../../stylesheet.css.
...and so on...
I'm stuck here:
$depth = $file.getDepth($path) #> totally clueless here
I need $depth to contain the number of folders under the root $path.
How can I get this? Here's the rest of my code:
$thisLocation = Get-Location
$path = Join-Path -path $thisLocation -childpath "\Files\depth0"
$match = "findThisInFiles"
$fragment = "stylesheet.css" #> string to be prefixed n times
$prefix = "../" #> prefix n times according to folder depth starting at $path (depth 0 -> don't prefix)
$replace = "" #> this will replace $match in files
$depth = 0
$htmlFiles = Get-ChildItem $path -Filter index*.html -recurse
foreach ($file in $htmlFiles)
{
$depth = $file.getDepth($path) #> totally clueless here
$replace = ""
for ($i=0; $i -lt $depth; $i++){
$replace = $replace + $prefix
}
$replace = $replace + $fragment
(Get-Content $file.PSPath) |
Foreach-Object { $_ -replace $match, $replace } |
Set-Content $file.PSPath
}
Here's a function I've written that uses Split-Path recursively to determine the depth of a path:
Function Get-PathDepth ($Path) {
$Depth = 0
While ($Path) {
Try {
$Parent = $Path | Split-Path -Parent
}
Catch {}
if ($Parent) {
$Depth++
$Path = $Parent
}
else {
Break
}
}
Return $Depth
}
Example usage:
$MyPath = 'C:\Some\Example\Path'
Get-PathDepth -Path $MyPath
Returns 3.
Unfortunately, I had to wrap Split-Path in a Try..Catch because if you pass it the root path then it throws an error. This is unfortunate because it means genuine errors won't cause an exception to occur but can't see a way around this at the moment.
The advantage of working using Split-Path is that you should get a consistent count regardless of whether a trailing \ is used or not.
Here is a way to get the depth in the folder structure for all files in a location. Hope this helps get you in the right direction
New-Item -Path "C:\Logs\Once\Test.txt" -Force
New-Item -Path "C:\Logs\Twice\Folder_In_Twice\Test.txt" -Force
$Files = Get-ChildItem -Path "C:\Logs\" -Recurse -Include *.* | Select-Object FullName
foreach ($File in $Files) {
[System.Collections.ArrayList]$Split_File = $File.FullName -split "\\"
Write-Output ($File.FullName + " -- Depth is " + $Split_File.Count)
}
Output is this just for illustration
C:\Logs\Once\Test.txt -- Depth is 4
C:\Logs\Twice\Folder_In_Twice\Test.txt -- Depth is 5

Powershell output formatting?

I have a script that scans for a specific folder in users AppData folder. If it finds the folder, it then returns the path to a txt file. So we can see the computer name and username where it was found.
I would like to be able to format the what is actually written to the text file, so it removes everything from the path except the Computer and User names.
Script:
foreach($computer in $computers){
$BetterNet = "\\$computer\c$\users\*\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm"
Get-ChildItem $BetterNet | ForEach-Object {
$count++
$betternetCount++
write-host BetterNet found on: $computer
Add-Content "\\SERVERNAME\PowershellScans\$date\$time\BetterNet.txt" $_`n
write-host
}
}
The text files contain information like this
\\computer-11-1004S10\c$\users\turtle\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm
\\computer-1004-24S\c$\users\camel\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm
\\computer-1004-23S\c$\users\rabbit\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm
If you have each line in a form of the string $string_containing_path then it is easy to split using split method and then add index(1) and (4) that you need:
$afterSplit = $string_containing_path.Split('\')
$stringThatYouNeed = $afterSplit[1] + " " + $afterSplit[4]
You can also use simple script that will fix your current logs:
$path_in = "C:\temp\list.txt"
$path_out= "C:\temp\output.txt"
$reader = [System.IO.File]::OpenText($path_in)
try {
while($true){
$line = $reader.ReadLine()
if ($line -eq $null) { break }
$line_after_split_method = $line.Split('\')
$stringToOutput = $line_after_split_method[1] + " " + $line_after_split_method[4] + "`r`n"
add-content $path_out $stringToOutput
}
add-content $path_out "End"
}
finally {
$reader.Close()
}
If you split your loop into two foreach loops, one for computer and user directory it would be easier to output the name of the user directory.
$output = foreach($computer in $computers){
$UserDirectories = Get-ChildItem "\\$computer\c$\users\" -Directory
foreach ($Directory in $UserDirectories) {
$BetterNet = Get-ChildItem (Join-Path $Directory.fullname "\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm")
Add-Content "\\SERVERNAME\PowershellScans\$date\$time\BetterNet.txt" "$computer $($Directory.name)`r`n"
write-host BetterNet found on: $computer
$BetterNet
}
}
$output.count

Using Context in Powershell Select-String

I have a script that searches for a series of strings (stored in a txt file) in the contents of files in a directory. I would like to modify it to also list the text around the string found (these are regular strings, not regex expressions). I played around a lot and it seems like I need to use -Context, but I am not sure how to get the text from that.
Also, the files I am searching may not have linefeeds, so if it could just get the xx characters before and after the search term, that would be better.
Here's what I have so far (I omitted the looping though files parts):
$result = Get-Content $file.FullName | Select-String $control -quiet
If ($result -eq $True)
{
$match = $file.FullName
"Match on string : $control in file : $match" | Out-File $output -Append
Write-host "Match on string : $control in file : $match"
}
If it could write the context, that would be perfect. Seems like I need to use $_Matches, but not sure how.
If $control is just a regular string, can you turn it into a regular expression?
$n = 3
$re = "(.{0,$n})(" + [Regex]::Escape($control) + ")(.{0,$n})"
$result = (Get-Content $file.FullName) -match $re
With this, the $matches hashtable should give you access to the $n characters before and after the match:
if ($result.Length -gt 0) {
echo "Before: $($matches[1])"
echo "After: $($matches[3])"
}
Here is what I have now and it seems to work:
$regex = "[\s\S]{0,$ContextChars}$SearchTerm[\s\S]{0,$ContextChars}"
$results = Get-Content $file.FullName | Select-String -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value }
if ($results)
{
foreach($result in $results)
{
$display = $result
"File: $file Match ---$display---"
}
}
The only thing I wish I had but don't know how to get it is the line number the match is found on.