Powershell - Append text to end of filenames in sub-directories recursively - powershell

Overview
Hi StackOverflow!
I'm hoping someone on here could help me.
I have built the following code to append text to the end of filename on a network drive.
The files in question are primarily ".zip" files and usually range from 6 - 8 sub-directories in the parent folder with 8 - 10 ".zip" files per sub-directoy.
What I am hoping to achieve is to append text to the end of each filename recursively.
Code:
#Current Directory of creatives
$fileLocation = read-host "Type/Paste location of creatives"
if (Test-Path \\serverPath\name\* -PathType Leaf) {
$serverPathName = "\\serverPath\name\"
$driveLetter = "D:\"
$fileLocation = ($fileLocation -replace [regex]::Escape($serverPathName),$driveLetter)
}
$fileLocation = Resolve-Path $fileLocation
Write-Output $fileLocation
$newText = read-host "Please enter text to append"
$newText = $newText -replace '\s','-'
$addMarket = read-host "Are folders split by market? [Y/N]"
$ZipFiles = Get-ChildItem -Path "$currentDirectory" -Recurse -Filter "*.zip"
if ($addMarket -eq "N") {
foreach ($Zipfile in $ZipFiles) {
Get-ChildItem | %{$_|rename-item -NewName ((($_.BaseName -replace '\s','-') + "-" + $newText + $_.Extension))}
}
}
elseif($addMarket -eq "Y") {
foreach ($Zipfile in $ZipFiles) {
Get-ChildItem -File -Recurse | Rename-Item -NewName {(($_.BaseName -replace '\s','-') + "-" + (($_.Directory.Name -replace '\s','-')) + "-" + $newText + $_.Extension).ToLower()}
}
}
else {
write-host "ERROR! Incorrect Input!"
Write-Host "Exiting Script..."
Exit
}
#Clear Console
clear
#Read-Host -Prompt “Press Enter to exit”
Current Situation:
When I run the above (without the loop function), it works fine but I have to into each sub-directory and run the script directly there - which is obviously time-consuming and monotonous.
When I run it within the loop, it works but loops for a long time until the total count of ".zip" files have been reached. e.g. 8 sub-dirs, 9 ".zip" = loops 72 times - each time appending the same text.
Expected Situation:
I wish to run the script from the parent folder of the sub-dirs and the script will only append the text once per sub-dir to all ".zip" and then exit the script.
Problem(?)
I believe the issue lies in the following variable:
$ZipFiles
But I am unable to figure out how to rectify this. I've set it to find all ".zip" files in the parent folder. I've also set it to count he number of files per sub-dir:
Get-ChildItem -Directory | ForEach-Object { Write-Host $_.FullName $(Get-ChildItem $_ | Measure-Object).Count}
But neither has worked for me.
Summary:
I'm hoping someone can point out to me the error I am making and where the fix needs to be.
I am open to all suggestions. If there is a better approach, please also let me know.
I don't mind changing how the code works, if it means the same end result.
Thanks for reading!
Rajiv Ahmed

You have some errors/mistakes in your code:
- You ask to provide a $fileLocation but you don't use it in your code.
- Instead you use a variable $currentDirectory but that's not defined.
- Then you collect all zip files in a variable $ZipFiles, you iterate over this array where you already have zip files but you use Get-ChildItem to get them again. ;-)
Something like this should work actually:
$fileLocation = read-host "Type/Paste location of creatives"
if (Test-Path \\serverPath\name\* -PathType Leaf) {
$serverPathName = "\\serverPath\name\"
$driveLetter = "D:\"
$fileLocation = ($fileLocation -replace [regex]::Escape($serverPathName), $driveLetter)
}
$fileLocation = Resolve-Path $fileLocation
Write-Output $fileLocation
$newText = read-host "Please enter text to append"
$newText = $newText -replace '\s', '-'
$addMarket = read-host "Are folders split by market? [Y/N]"
if ($addMarket -eq "N") {
Get-ChildItem -Path $fileLocation -Recurse -Filter "*.zip" -File |
ForEach-Object {
Rename-Item -Path $_.FullName -NewName ((($_.BaseName -replace '\s', '-') + "-" + $newText + $_.Extension))
}
}
elseif ($addMarket -eq "Y") {
Get-ChildItem -Path $fileLocation -Recurse -Filter "*.zip" -File |
ForEach-Object {
Rename-Item -Path $_.FullName -NewName ((($_.BaseName -replace '\s', '-') + "-" + (($_.Directory.Name -replace '\s', '-')) + "-" + $newText + $_.Extension).ToLower())
}
}
else {
write-host "ERROR! Incorrect Input!"
Write-Host "Exiting Script..."
Exit
}
Clear-Host

Related

batch rename using windows powershell

I am busy with a windows powershell script to batch rename a bunch of folders with IDs, which works perfectly fine. My question is how do i add validation to ensure all the IDs are unique when all my files get renamed. Here is the code:
param(
[Parameter(Mandatory=$true)]
[int]$idx
)
Get-ChildItem *.sql | Foreach-Object {
$iPrefix = ('{0:d5}' -f $idx)
$path = (Split-Path -Path($_))
$filename = (Split-Path -Path($_) -Leaf) -replace "\[|\]",""
#%{ write-host $path}
%{ write-host $filename}
if(!$filename.StartsWith("script","CurrentCultureIgnoreCase"))
{
#%{ write-host "Script$iPrefix - $filename"}
Rename-Item -LiteralPath(($_)) -NewName("Script$iPrefix - $filename")
++$idx
%{ write-host "Renamed: " + $filename}
}
}
Here is a screenshot of what i want to avoid:
As you can see Script02185 is repeated twice, because the script was ran at two different times. How do i ensure that the numbers will remain unique?
Try this.
$files = Get-ChildItem . -Filter *.sql
$prefixedFiles, $unprefixedFiles = $files.Where({ $_.Name -match "^Script\d{5} - " }, 'split')
$usedIDs = [int[]]$prefixedFiles.Name.Substring(6, 5)
$unusedIDs = [System.Collections.Generic.HashSet[int]](1..99999)
$unusedIDs.ExceptWith($usedIDs)
$enumerator = $unusedIDs.GetEnumerator()
$unprefixedFiles | Rename-Item -NewName {
if (!$enumerator.MoveNext()) { throw "`nThere are no unused IDs." }
"Script{0:D5} - {1}" -f $enumerator.Current, ($_.Name -replace '\[|\]')
} -ErrorAction Stop -PassThru

Copy file based a specified folder based on file name. Create folder if it doesn't exist

I'm trying to copy files to a specific folder based on a file name.
For example:
Current Folder - C:\Stuff\Old Files\
The File- 206.Little Rock.map.pdf
Destination Folder - D:\Cleanup\206\Repository
So basically the leading number on the file (206) is part of the subfolder. The "\Repository" would stay constant. Only the leading number would change.
If the file was 207.Little Rock.map.pdf then the destination folder would be
D:\Cleanup\207\Repository
I started with a code I got from here but I'm not sure how to account for the change in number and how to make it create a folder if the folder doesn't exist. So 206\Repository would probably already exist, but I would need the script to create the folder if it doesn't.
$SourceFolder = "C:\Stuff\Old Files\"
$targetFolder = "D:\Cleanup\"
$numFiles = (Get-ChildItem -Path $SourceFolder -Filter *.pdf).Count
$i=0
clear-host;
Write-Host 'This script will copy ' $numFiles ' files from ' $SourceFolder ' to ' $targetFolder
Read-host -prompt 'Press enter to start copying the files'
Get-ChildItem -Path $SourceFolder -Filter *.PDF | %{
[System.IO.FileInfo]$destination = (Join-Path -Path $targetFolder -ChildPath $Name.Repository(".*","\"))
if(!(Test-Path -Path $destination.Directory )){
New-item -Path $destination.Directory.FullName -ItemType Directory
}
[int]$percent = $i / $numFiles * 100
copy-item -Path $_.FullName -Destination $Destination.FullName
Write-Progress -Activity "Copying ... ($percent %)" -status $_ -PercentComplete $percent -verbose
$i++
}
Write-Host 'Total number of files read from directory '$SourceFolder ' is ' $numFiles
Write-Host 'Total number of files that was copied to '$targetFolder ' is ' $i
Read-host -prompt "Press enter to complete..."
clear-host;
This should do mostly what you need. You might have to tweak the destination path a bit, but that should be fairly straight forward to figure out. I Highly recommend that use a '-' as the delimiter for your file prefix as opposed to a '.' as this will prevent accidentally moving EVERY FILE in a directory if you happen to execute it in the wrong place.
Also, when you write a script, do create functions to do individual units of work, and then call those functions at the end. It's much easier to modify, and debug that way.
<#
.SYNOPSIS
Moves files from source to destination based on FileName
Creates destination folder if it does not exist.
.DESCIPTION
The script expects files with a prefix defined by a hyphen '-' i.e. 200-<filename>.<ext>.
There is no filename validation in this script; it will *probably* skip files without a prefix.
A folder based on the prefix will be created in the destination.
If your file is name string-cheese.txt then it will be moved to $DestinationIn\string\string-cheese.txt
.PARAMETER SourceIn
Source Path (folder) where your files exist.
.PARAMETER DestinationIn
Target Path (folder) where you want your files to go.
.EXAMPLE
& .\CleanUp-Files.ps1 -SourceIn "C:\Users\User\Documents\Files\" -DestinationIn "C:\Users\User\Documents\Backup\" -Verbose
.NOTES
Author: RepeatDaily
Email: RepeatedDaily#gmail.com
This script is provided as is, and will probably work as intended. Good Luck!
https://stackoverflow.com/questions/50662140/copy-file-based-a-specified-folder-based-on-file-name-create-folder-if-it-doesn
#>
[CmdletBinding()]
param (
[string]$SourceIn,
[string]$DestinationIn
)
function Set-DestinationPath {
param (
[string]$FileName,
[string]$Target
)
[string]$NewParentFolderName = $FileName.SubString(0,$FileName.IndexOf('-'))
[string]$DestinationPath = Join-Path -Path $Target -ChildPath $NewParentFolderName
return $DestinationPath
}
function Create-DestinationPath {
[CmdletBinding()]
param (
[string]$Target
)
if (-not(Test-Path -Path $Target)) {
Try {
New-Item -ItemType Directory -Path $Target | Write-Verbose
}
catch {
Write-Error $Error[0];
}
}
else {
Write-Verbose "$Target exists"
}
}
function Move-MyFiles {
[CmdletBinding()]
param (
[string]$Source,
[string]$Destination
)
[array]$FileList = Get-ChildItem $Source -File | Select-Object -ExpandProperty 'Name'
foreach ($file in $FileList) {
[string]$DestinationPath = Set-DestinationPath -FileName $file -Target $Destination
Create-DestinationPath -Target $DestinationPath
try {
Move-Item -Path (Join-Path -Path $Source -ChildPath $file) -Destination $DestinationPath | Write-Verbose
}
catch {
Write-Warning $Error[0]
}
}
}
Move-MyFiles -Source $SourceIn -Destination $DestinationIn
Here is something you might try. The number for the directory is grabbed from a regex match, "(\d+)\..*.pdf". When you are confident that the correct file copies will be made, remove the -WhatIf from the Copy-Item cmdlet.
I did not try to address the Write-Progress capability. Also, this will only copy .pdf files that begin with digits followed by a FULL STOP (period) character.
I do not fully understand the need for all of the Write-Host and Read-Host usage. It is not very PowerShell. pwshic
$SourceFolder = 'C:/src/t/copymaps'
$targetFolder = 'C:/src/t/copymaps/base'
$i = 0
$numFiles = (
Get-ChildItem -File -Path $SourceFolder -Filter "*.pdf" |
Where-Object -FilterScript { $_.Name -match "(\d+)\..*.pdf" } |
Measure-Object).Count
clear-host;
Write-Host 'This script will copy ' $numFiles ' files from ' $SourceFolder ' to ' $targetFolder
Read-host -prompt 'Press enter to start copying the files'
Get-ChildItem -File -Path $SourceFolder -Filter "*.pdf" |
Where-Object -FilterScript { $_.Name -match "(\d+)\..*.pdf" } |
ForEach-Object {
$NumberDir = Join-Path -Path $targetFolder -ChildPath $Matches[1]
$NumberDir = Join-Path -Path $NumberDir -ChildPath 'Repository'
if (-not (Test-Path $NumberDir)) {
New-Item -ItemType Directory -Path $NumberDir
}
Copy-Item -Path $_.FullName -Destination $NumberDir -Whatif
$i++
}
Write-Host 'Total number of files read from directory '$SourceFolder ' is ' $numFiles
Write-Host 'Total number of files that was copied to '$targetFolder ' is ' $i
Read-host -prompt "Press enter to complete..."
clear-host;

list files that contain a string from file and modified date is last 7 days

I have a folder that contains a bunch of files that has pictures and the naming format is similar to:
kitty_BW.jpg
puppy_B_W.jpg
ball_BlWh.jpg
rope_bw.jpg
shoe_Bw.jpg
kitty_C.jpg
puppy_Color.jpg
ball_c.jpg
rope_color.jpg
shoe_col.jpg
to move the files that I need I created a list
kitty
puppy
ball
rope
shoe
so what I need is to move the files that are NOT in a TXT file list AND are older than this month.
I was starting out with this code
$file_list = Get-Content C:\photoList.txt
$search_folder = "D:\photos"
$destination_folder = "C:\Backup"
foreach ($file in $file_list) {
$file_to_move = Get-ChildItem -Path $search_folder -Filter $file -Recurse -ErrorAction SilentlyContinue -Force | % { $_.FullName}
if ($file_to_move) {
Move-Item $file_to_move $destination_folder
}
}
but this only works if the whole name of the file is in the list and the file that is being moved is ON the list, but I want to move files that are not on the list and are older than this month so I can dispose of them.
EDIT
I've been trying all sorts of things but for some reason, it doesn't compare
$file_list = "C:\FAphotoList.txt"
$search_folder ="C:\FAphotos"
$destination_folder = "C:\Backup\"
$first_day = $(Get-Date -UFormat "%Y / %m ") + "/ 01"
$list = (Get-ChildItem $search_folder | ? LastWriteTime -lt $first_day).name
echo $list | Select-String -Pattern '0183444'
Get-Content $file_list | % {
$list2 = ($list | Select-String -Pattern $_)
}
echo list2
echo $list2
#here is my problem
$list2 | % {
[array]::indexof($list,$_)
}
echo "final list"
echo $list
the problem is that when i try to compare the list with [array]::indexof($list, "kitty_BW.jpg")
it works, but when I try it like in the sample I get nothing.
EDIT 2
$file_list = "C:\FAphotoList.txt"
$search_folder ="C:\FAphotos"
$destination_folder = "C:\Backup\"
$first_day = $(Get-Date -UFormat "%Y / %m ") + "/ 01"
$list = (Get-ChildItem $search_folder | ? LastWriteTime -lt $first_day).name
echo lista
Get-Content $file_list | % {
$list.Remove(($list | Select-String -Pattern $_))
}
$list | % {
Move-Item $file_to_move $destination_folder
}
echo "final list"
$list = (Get-ChildItem $search_folder | ? LastWriteTime -lt $first_day).name
echo $list
gets me this error
Exception calling "Remove" with "1" argument(s): "Collection was of a fixed size."
At C:\FAphotos.ps1:17 char:5
+ $list += $list.Remove(($list | Select-String -Pattern $_))
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : NotSupportedException
so I was trying to cast to an array list, but that didn't work so I was trying lots of different things.
You have 2.5 main tasks:
Determine files that are older then this month, this task includes half a task: how do you get the first day of this month?
Compare which file name are NOT in the .txt file.
$first_day = $(Get-Date -Format "yyyy/mm") + "/01"
$list = (Get-ChildItem | ? LastWriteTime -lt $first_day).Name
Get-Content C:\photolist.txt | % {
$list.Remove(($list | Select-String -Pattern $_))
}
$list | % {
Move-Item $file_to_move $destination_folder
}
Not tested, please have a try, and let us know the result. Good luck! :)
After a little bit of digging and asking, I finally got it to work.
$baseWorkFolder = 'C:';
$searchFolder = Join-Path $baseWorkFolder "FAphotos";
$destFolder = Join-Path $baseWorkFolder "Backup";
$LastYear = '1/1/' + $(Get-Date -UFormat "%Y");
Write-Host "LastYear: $LastYear";
[string[]] $fileList = Get-Content "$($baseWorkFolder)\FAphotoList.txt";
Write-Host "FileList:`n`t$fileList" -foregroundcolor Green;
[string[]] $FilesInSearchFolder = Get-ChildItem $searchFolder -name;
Write-Host "FilesInfolder:`n`t$FilesInSearchFolder" -foregroundcolor Green;
$moveList = New-Object System.Collections.Generic.HashSet[String]
foreach ($file in $FilesInSearchFolder ){
$moveList.add($file)
}
Write-Host "FilesInfolder:`n`t$moveList" -foregroundcolor Green;
foreach ( $fileName in $FilesInSearchFolder ) {
foreach ( $empID in $fileList ) {
Write-Host "`n`tComparing Strings $fileName - $empID = $($fileName.IndexOf($empID))";
if ($fileName.IndexOf($empID) -ge 0) {
$moveList.remove($fileName);
}
}
}
Write-Host "Potential Remove List:`n`t$moveList" -foregroundcolor Red;
foreach ( $removeItem in $moveList ) {
$removegcItem = Get-ChildItem -Path $(Join-Path $searchFolder $removeItem);
Write-Host "`n`tProcessing $($removegcItem.FullName)";
Write-Host "`tChecking if file is older than $LastYear" -foregroundcolor Green;
if ($removegcItem.LastWriteTime -lt $LastYear) {
Write-Host "`tMoving $removegcItem to $destFolder";
Move-Item "$($removegcItem.FullName)" "$(Join-Path $destFolder $($removegcItem.Name))" ;
}
}
Write-Host "Final list (Newer than $LastYear):";
Write-Host "`t$( ( Get-ChildItem $searchFolder | Where-Object { $_.LastWriteTime -lt $LastYear } ).name )" `
-Foregroundcolor Yellow -BackgroundColor Black;
had to use hash set and another array because I got an error of not being a resizeable object.
I think I wasn't able to compare because I was comparing strings to paths.

How to move a file to a different folder based on user input in powershell

HI Everyone I am trying to write a script for my powershell practice to move a file from one folder to another based on user input. The script works fine but only if the file exists in the folder same as the script. Can someone please help me what i am doing wrong or try to help with logic
$destination = 'P:\Powershell practice\Movefolder'
$ListFile = "P:\Powershell practice\"
get-childitem $ListFile
$Filename = Read-host -prompt "Please Enter File to be moved"
$FileExists = test-path $Filename
If ($FileExists -eq $True) {
move-item $Filename $destination
Write-Host "File is moved to $destination "
}
else
{write-host "No File Found"}
You need to determine the full file path otherwise the test-path check whether the file exists at the current location. You can use the Join-Path cmdlet to join the pass:
$FilePath = Join-Path $ListFile $FileName
$FileExists = test-path $FilePath
If ($FileExists -eq $True) {
move-item $FilePath $destination
...
Offtopic:
You could also use a Grid-View where the user can select multiple files and press the ok button at the bottom to copy the files ;-):
$destination = 'P:\Powershell practice\Movefolder'
$ListFile = 'P:\Powershell practice\'
$filesToMove = get-childitem $ListFile | Out-GridView -OutputMode Multiple
$filesToMove | % { move-item $_.FullName $destination}
Incase if someone needs. Below is the correct working script for this
$destination = 'P:\Powershell practice\Movefolder'
$ListFile = 'P:\Powershell practice\'
get-childitem $ListFile
$Filename = Read-host -prompt "Please Enter File to be moved"
$FilePath = Join-Path $ListFile $FileName
$FileExists = test-path $FilePath
If ($FileExists -eq $True) {
move-item $FilePath $destination
Write-Host "File is moved to $destination "
}
else
{write-host "No File Found"}

Piped dir within Foreach

I'm writing a script to delete pdf files older than 6 months in folder with the 'Email' prefix.
However, my second dir command within my foreach never runs, its code is blocked.
$Now = Get-Date;
$DaysTillDelete = "180";
$LastWrite = $Now.AddDays(-$DaysTillDelete);
$TargetFolder = "C:\Test EMDATA\EMDATA\";
$BackupPath = "\\SHPFS02\IT\EmPower Old";
$EmailFolders = #();
if(-Not(Test-Path -path ($TargetFolder + "\OldFiles" ))) {
mkdir -p ($TargetFolder +"\OldFiles");
}
$Network = Test-Path $BackupPath
#New-PSDrive -Name O -PSProvider FileSystem -Root "$BackupPath"; #-Credential $cred
Write-Host "Running Script"
dir $TargetFolder | %{
# Only delete files with the Email prefix
$name = $_.Name;
if ($_.Name.Length -le 5) {return;}
$id = $_.Name.SubString(0,5);
if ($id -eq "Email")
{
Write-Host "Found slip folder"
$EmailFolders += $TargetFolder + $_;
}
}
ForEach ($folder in $EmailFolders)
{
Write-Host $folder;
dir -path $folder -include *.pdf | %{
Write-Host "Checking" $name;
# Only select files older than 6 months
if( $_.LastWriteTime -le "$LastWrite")
{
$activeItem = Get-Item $TargetFolder + $_;
#Move files into oldfiles
Write-Host $TargetFolder
move-item -path $activeItem -destination ($TargetFolder + "OldFiles\");
if ($Network)
{
move-item -path $activeItem -destination "O:\";
}
Write-Host $_;
remove-item $activeItem;
Write-Host "Deleting" + $name;
}
}
}
The script works till line 31 but doesn't continue on past line 32 and being a fairly beginner PS user I can't see why.
Only use -include with the -recurse parameter.
http://technet.microsoft.com/en-us/library/hh849800.aspx
The Include parameter is effective only when the command includes the
Recurse parameter or the path leads to the contents of a directory,
such as C:\Windows*, where the wildcard character specifies the
contents of the C:\Windows directory.
What you want instead is the -filter parameter:
dir -path $folder -filter *.pdf