How to prevent PowerShell -Recurse from renaming first file twice? - powershell

When using powershell to rename files with their directory name and file name, my code works, except in the first file in a directory, it gives it two copies of the directory name. So the file book1.xlsx in folder folder1 should become folder1book1.xlsx but it becomes folder1folder1book1.xlsx. The remaining files in folder1 are correctly named folder1book2.xlsx, folder1book3.xlsx, etc.
I have a directory, with many sub-directories. In each sub-dir are files that need their sub-dir name added in.
I've been following this code. For me it looks like:
dir -Filter *.xlsx -Recurse | Rename-Item -NewName {$_.Directory.Name + "_" + $_.Name}
I've also tried
--setting the Recurse -Depth 1 so that it doesn't keep looking for folders in the sub-folders.
--using ForEach-Object {$_ | ... after the pipe, similar to this.
--running it in Visual Studio Code rather than directly in PowerShell, which turns it into:
Get-ChildItem "C:\my\dir\here" -Filter *.xls -Recurse | Rename-Item -NewName {$_.DirectoryName + '_' + $_.Name}
--putting an empty folder inside the sub-directory, setting -Depth 2 to see if that will "catch" the recurse loop
I would expect the files to be named folder1_book1.xlsx, folder1_book2.xlsx, folder1_book3.xlsx.
But all of the attempted changes above give the same result. The first file is named folder1_folder1_book1.xlsx [INCORRECT], folder1_book2.xlsx[CORRECT], folder1_book3.xlsx[CORRECT].
A workaround might be writing an if statement for "not files that contain the sub-directory name" as suggested here. But the link searches for a text string not an object (probably not the correct term) like #_.Directory.Name. This post shows how to concatenate objects but not something like #_.Directory.Name. Having to put in an if statement seems like an unnecessary step if -Recurse worked the way it should, so I'm not sure this workaround gets at the heart of the issue.
I'm running windows 10 with bootcamp on a 2018 iMac (I'm in Windows a lot because I use ArcMap). Powershell 5.1.17134.858. Visual Studio Code 1.38.0. This is a task I would like to learn how to use more in the future, so explanations will help. I'm new to using PowerShell. Thanks in advance!

This was a script I created for one of my customers that may help
<##################################################################################################################################
This script can be used to search through folders to rename files from their
original name to "filename_foldername.extension". To use this script
please configure the items listed below.
Items to Congfigure
-$Original
-$Source
-$Destination
-$Files
Also please change the Out-File date on line 29 to today's date ****Example: 2019-10-02****
We've also added a change log file that is named "FileChange.txt" and can be found in the location identified on line 30
>
$Original="C:\temp\test" #Location of ".cab" files copied
$Source="C:\temp\Test" #Location were ".cab" files are stored
$Destination="C:\temp\Test\2019-10-02" #Location were you want to copy ".cab" files after the file name change. Be sure to change the date to the date you run this script. The script creates a folder with todays date
$Files=#("*.cab") #Choose the file type you want to search for
$ErrorActionPreference = "SilentlyContinue" #Suppress Errors
Get-ChildItem $Original -Include "*.cab" -File -Recurse | Rename-Item -NewName {$_.BaseName+"_"+$_.Directory.Name +'.cab'}
New-Item -ItemType Directory -Path ".\$((Get-Date).ToString('yyyy-MM-dd'))"; Get-ChildItem -recurse ($Source) -include ($Files) | Copy-Item -Destination ($Destination) -EA SilentlyContinue
Get-ChildItem $Original | Where {$_.LastWriteTime -ge [datetime]::Now.AddMinutes(-10)} | Out-File C:\temp\test\2019-10-02\FileChange.txt

Related

Powershell: Find Folders and Run Command in Those Folders

so trying to find a way to combine a couple of things the Stack Overflow crowd has helped me do in the past. So I know how to find folders with a specific name and move them where I want them to go:
$source_regex = [regex]::escape($sourceDir)
(gci $sourceDir -recurse | where {-not ($_.psiscontainer)} | select -expand fullname) -match "\\$search\\" |
foreach {
$file_dest = ($_ | split-path -parent) -replace $source_regex,$targetDir
if (-not (test-path $file_dest)){mkdir $file_dest}
move-item $_ -Destination $file_dest -force -verbose
}
And I also know how to find and delete files of a specific file extension within a preset directory:
Get-ChildItem $source -Include $searchfile -Recurse -Force | foreach{ "Removing file $($_.FullName)"; Remove-Item -force -recurse $_}
What I'm trying to do now is combine the two. Basically, I'm looking for a way to tell Powershell:
"Look for all folders named 'Draft Materials.' When you find a folder with that name, get its full path ($source), then run a command to delete files of a given file extension ($searchfile) from that folder."
What I'm trying to do is create a script I can use to clean up an archive drive when and if space starts to get tight. The idea is that as I develop things, a lot of times I go through a ton of incremental non-final drafts (hence folder name "Draft Materials"), and I want to get rid of the exported products (the PDFs, the BMPs, the AVIs, the MOVs, atc.) and just leave the master files that created them (the INDDs, the PRPROJs, the AEPs, etc.) so I can reconstruct them down the line if I ever need to. I can tell the script what drive and folder to search (and I'd assign that to a variable since the backup location may change and I'd like to just change it once), but I need help with the rest.
I'm stuck because I'm not quite sure how to combine the two pieces of code that I have to get Powershell to do this.
If what you want is to
"Look for all folders named 'Draft Materials.' When you find a folder with that name, get its full path ($source), then run a command to delete files of a given file extension ($searchfile) from that folder."
then you could do something like:
$rootPath = 'X:\Path\To\Start\Searching\From' # the starting point for the search
$searchFolder = 'Draft Materials' # the folder name to search for
$deleteThese = '*.PDF', '*.BMP', '*.AVI', '*.MOV' # an array of file patterns to delete
# get a list of all folders called 'Draft Materials'
Get-ChildItem -Path $rootPath -Directory -Filter $searchFolder -Recurse | ForEach-Object {
# inside each of these folders, get the files you want to delete and remove them
Get-ChildItem -Path $_.FullName -File -Recurse -Include $deleteThese |
Remove-Item -WhatIf
}
Or use Get-ChildItem only once, having it search for files. Then test if their fullnames contain the folder called 'Draft Materials'
$rootPath = 'X:\Path\To\Start\Searching\From'
$searchFolder = 'Draft Materials'
$deleteThese = '*.PDF', '*.BMP', '*.AVI', '*.MOV'
# get a list of all files with extensions from the $deleteThese array
Get-ChildItem -Path $rootPath -File -Recurse -Include $deleteThese |
# if in their full path names the folder 'Draft Materials' is present, delete them
Where-Object { $_.FullName -match "\\$searchFolder\\" } |
Remove-Item -WhatIf
In both cases I have added safety switch -WhatIf so when you run this, nothing gets deleted and in the console is written what would happen.
If that info shows the correct files are being removed, take off (or comment out) -Whatif and run the code again.

Rename bulk files with given name, in directory and subfolders using PowerShell

I’m looking for a solution to rename all files with given name in the directory and subfolders.
E.g.
There is a directory called c:\blah and in this folder, there is one file called BaR.txt and one folder called foo. In the foo folder, there is another file called bAr.txt.
I want to find all files with name bar.txt and change the name of each file to “neo.txt”. It must also be possible to rename the files to lower case as well e.g. bar.txt.
So far, I’ve tried to do this manually using the Windows Explorer in Windows10 but for some weird reason when I try to rename the bulk files there is extra “(1)” sign added to the end of each file name.
I’ve tried to play with PowerShell and I created a command
$_.name -replace 'bar.txt','neo.txt'
But it didn’t work for me.
To do this, you need 2 cmdlets: Get-ChildItem and Rename-Item.
This should work:
Get-ChildItem -Path 'C:\Blah' -Filter 'bar.txt' -Recurse | ForEach-Object {
Rename-Item -Path $_.FullName -NewName 'neo.txt'
}
However, if inside the foo folder there are more subfolders, this code will rename all files called bar.txt (case-insensitive) it finds in all of these subfolders.
If you do not want to go any deeper than the first subfolder in C:\Blah (the one called foo), you can use the -Depth parameter on the command like this:
Get-ChildItem -Path 'C:\Blah' -Filter 'bar.txt' -Recurse -Depth 1 | ForEach-Object {
Rename-Item -Path $_.FullName -NewName 'neo.txt'
}

PowerShell Script finding File using extension and excluding a folder

I am using the below code:
Get-ChildItem -Path N:\USERS -Filter DANTOM.DTM -Recurse -ErrorAction SilentlyContinue -Force
I need it to either find the file "DANTOM.DTM" or the extension ".DTM". I need to exclude the folder N:\USERS\EDI because it is a 1.7TB folder that would never have this file in it. So in doing so would really speed up the process.
I would like the end result to either spit into a .txt file saying which folders inside of N:\USERS has the file or just have it display as a list in powershell.
Thank you,
Assuming that the files of interest do not reside directly in N:\USERS (only in subdirs.), try the following (PSv3+ syntax); send to a file by appending > dirs.txt, for instance.
Get-ChildItem N:\USERS -Directory | ? Name -ne 'EDI' |
Get-ChildItem -Recurse -Filter *.DTM |
ForEach-Object { $_.DirectoryName }
Note: While it is tempting to try a simpler approach with -Exclude EDI, it unfortunately doesn't seem to be effective in excluding the entire subtree of the EDI subfolder.

Get Relative paths of all the folders, sub-folders and files in a folder

I have a folder named source. It's structure is like the following.
source\a.jpg
source\b.jpg
source\c.xml
source\d.ps1
source\subdir1\a.xml
source\subdir2\b.png
source\subdir3\subsubdir1\nothing.img
I want to list all the relative paths of folders, sub-folders and files in a text file say, out.txt. For above the output I expect is:
source\a.jpg
source\b.jpg
source\c.xml
source\d.ps1
source\subdir1\a.xml
source\subdir2\b.png
source\subdir3\subsubdir1\nothing.img
source\subdir1
source\subdir2
source\subdir3
source\subdir3\subsubdir1
You can see that the output includes individual folders and sub-folders too.
Note: I am in a folder just outside the source folder. I mean for example I am in fold folder which contains source folder -> fold/source but if your solution includes putting the script inside the source folder, thats fine too. Both solutions are fine. This may be easy but I am not familiar with powershell but can at least run scripts from it if given.
EDIT1: Okay, in the "duplicate question" the answer is for relative paths of individual files. But I also want the folders and sub-folders.
EDIT2: Okay, I wrote a command:
(gci -path source -recurse *.|Resolve-path -relative) -replace "","" | out-file -FilePath output.txt -Encoding ascii
Now, this command gives me the relative name of only subdirectory inside the source(in the actual source folder of mine with a different name; source is a dummy name obviously!). What should I change in this code to get other names of files inside the subdirectory in source.
This is clean one line answer,no loops on my part to write, which does the job perfectly. I produced it by luck "playing around" a bit.
(gci -Path source -Recurse *.*|Resolve-Path -Relative) -replace "\.","" |
Out-File -FilePath output.txt -Encoding ascii
Not very clean but this might be an option.
(Get-ChildItem -Recurse -Path "C:\ABC\source\").FullName|ForEach{[Regex]::Replace($_,'^C:\\ABC\\','')}
I would probably do something like this:
$source = 'C:\path\to\source'
$parent = [IO.Path]::GetDirectoryName($source) -replace '\\+$'
$pattern = '^' + [regex]::Escape($parent) + '\\'
Get-ChildItem -Path $source -Recurse | % { $_.FullName -replace $pattern }

Rename first 20 characters of every filename in a file

I am trying to write a script in powershell to remove the first 20 characters of every MP3 filename in a folder, I have created a file 'test.ps' and inserted the powershell code below into it,
gci *.mp3 | rename-item -newname { [string]($_.name).substring(20) }
When I run this file in powershell.exe nothing happens,
Can anyone help? Thanks.
This may get you started. (There are probably much more concise ways, but this works and is readable when you need to maintain it later. :-) )
I created a folder C:\TempFiles, and created the following files in that folder:
TestFile1.txt
TestFile2.txt
TestFile3.txt
TestFile4.txt
(I created them the old-fashioned way, I'm afraid. <g>. I used
for /l %i in (1,1,4) do echo "Testing" > TestFile%i.txt
from an actual command prompt.)
I then opened PowerShell ISE from the start menu, and ran this script. It creates an array ($files), containing only the names of the files, and processes each of them:
cd \TempFiles
$files = gci -name *.txt
foreach ($file in $files) {
$thename = $file.substring(4);
rename-item -path c:\TempFiles\$file -newname $thename
}
This left the folder containing:
File1.Txt
File2.Txt
File3.Txt
File4.Txt
File5.Txt
In order to run a script from the command line, you need to change some default Windows security settings. You can find out about them by using PowerShell ISE's help file (from the menu) and searching for about_scripts or by executing help about_scripts from the ISE prompt. See the sub-section How To Run A Script in the help file (it's much easier to read).
Your code actually works. Two things...
Rename the file to test.ps1.
Run it in the folder you have your MP3 files in. Since you didn't provided a path to Get-ChildItem it will run in the current directory.
I tested your line by making a bunch of mp3 files like this -
1..30 | % { new-item -itemtype file -Name (
$_.ToString().PadLeft(30, 'A') + ".mp3" )}
I would use a more "safer" way (you'll get an error if the file name is shorter than the length in question, you are also targeting the file extension as a part of the total characters). Check if the base name of each file is greater than 21 characters (if you remove the first 20 it can be still have a name with one character long). It can fail if the directory contains a file with same name after you removed the first 20, you can develop it further on your own):
gci *.mp3 | foreach{
if($_.BaseName.Length -ge 21)
{
$ext = $_.Extension
$BaseName = $_.BaseName.Substring(20)
Rename-Item $_ -NewName "$BaseName$ext"
}
}
// delete (replace with empty char) first 20 charters in all filename witch is started with "dbo."
// powershell
Get-ChildItem C:\my_dir\dbo -Recurse -Force -Filter dbo.* | Where-Object {!$_.PSIsContainer} | Rename-Item -NewName { ($_.name).Substring(20) }