I want to print a directory tree excluding a folder. I already know the basic way to print the tree like this:
tree /A > tree.txt
I want to achieve something like this:
tree /A [exclude folder node_modules] > tree.txt
The standard tree.com utility does not support excluding directories.
If you only need to exclude directories by name themselves and not also their entire subtree (child directories and their descendants), see nferrell's answer.
If you need to exclude the entire subtree of directories matching a given name, more work is needed - see below.
Below is the source code for PowerShell function tree, which emulates the behavior of tree.com command, while also:
offering selective exclusion of subtrees by name
Note: You may specify multiple names separated by , and the names can be wildcard patterns - note that they only apply to the directory name, however, not the full path.
offering cross-platform support
Note: Be sure to save your script with UTF-8 encoding with BOM for the script to function properly without -Ascii.
offering switch -IncludeFiles to also print files.
With the function below loaded, the desired command looks like this:
tree -Exclude node_modules -Ascii > tree.txt
Run tree -? or Get-Help tree for more information.
### `tree` source code (add to your `$PROFILE`, for instance; PSv4+):
function tree {
<#
.SYNOPSIS
Prints a directory's subtree structure, optionally with exclusions. #'
.DESCRIPTION
Prints a given directory's subdirectory structure recursively in tree form,
so as to visualize the directory hierarchy similar to cmd.exe's built-in
'tree' command, but with the added ability to exclude subtrees by directory
names.
NOTE: Symlinks to directories are not followed; a warning to that effect is
issued.
.PARAMETER Path
The target directory path; defaults to the current directory.
You may specify a wildcard pattern, but it must resolve to a single directory.
.PARAMETER Exclude
One or more directory names that should be excluded from the output; wildcards
are permitted. Any directory that matches anywhere in the target hierarchy
is excluded, along with its subtree.
If -IncludeFiles is also specified, the exclusions are applied to the files'
names as well.
.PARAMETER IncludeFiles
By default, only directories are printed; use this switch to print files
as well.
.PARAMETER Ascii
Uses ASCII characters to visualize the tree structure; by default, graphical
characters from the OEM character set are used.
.PARAMETER IndentCount
Specifies how many characters to use to represent each level of the hierarchy.
Defaults to 4.
.PARAMETER Force
Includes hidden items in the output; by default, they're ignored.
.NOTES
Directory symlinks are NOT followed, and a warning to that effect is issued.
.EXAMPLE
tree
Prints the current directory's subdirectory hierarchy.
.EXAMPLE
tree ~/Projects -Ascii -Force -Exclude node_modules, .git
Prints the specified directory's subdirectory hierarchy using ASCII characters
for visualization, including hidden subdirectories, but excluding the
subtrees of any directories named 'node_modules' or '.git'.
#>
[cmdletbinding(PositionalBinding=$false)]
param(
[parameter(Position=0)]
[string] $Path = '.',
[string[]] $Exclude,
[ValidateRange(1, [int]::maxvalue)]
[int] $IndentCount = 4,
[switch] $Ascii,
[switch] $Force,
[switch] $IncludeFiles
)
# Embedded recursive helper function for drawing the tree.
function _tree_helper {
param(
[string] $literalPath,
[string] $prefix
)
# Get all subdirs. and, if requested, also files.
$items = Get-ChildItem -Directory:(-not $IncludeFiles) -LiteralPath $LiteralPath -Force:$Force
# Apply exclusion filter(s), if specified.
if ($Exclude -and $items) {
$items = $items.Where({ $name = $_.Name; -not $Exclude.Where({ $name -like $_ }, 'First') })
}
if (-not $items) { return } # no subdirs. / files, we're done
$i = 0
foreach ($item in $items) {
$isLastSibling = ++$i -eq $items.Count
# Print this dir.
$prefix + $(if ($isLastSibling) { $chars.last } else { $chars.interior }) + $chars.hline * ($indentCount-1) + $item.Name
# Recurse, if it's a subdir (rather than a file).
if ($item.PSIsContainer) {
if ($item.LinkType) { Write-Warning "Not following dir. symlink: $item"; continue }
$subPrefix = $prefix + $(if ($isLastSibling) { $chars.space * $indentCount } else { $chars.vline + $chars.space * ($indentCount-1) })
_tree_helper $item.FullName $subPrefix
}
}
} # function _tree_helper
# Hashtable of characters used to draw the structure
$ndx = [bool] $Ascii
$chars = #{
interior = ('├', '+')[$ndx]
last = ('└', '\')[$ndx] #'
hline = ('─', '-')[$ndx]
vline = ('│', '|')[$ndx]
space = ' '
}
# Resolve the path to a full path and verify its existence and expected type.
$literalPath = (Resolve-Path $Path).Path
if (-not $literalPath -or -not (Test-Path -PathType Container -LiteralPath $literalPath) -or $literalPath.count -gt 1) { throw "'$Path' must resolve to a single, existing directory."}
# Print the target path.
$literalPath
# Invoke the helper function to draw the tree.
_tree_helper $literalPath
}
Note:
The third-party Get-PSTree cmdlet (installable via Import-Module PSTree -Scope CurrentUser, for instance) offers a more fully featured implementation that notably includes reporting (cumulative) directory sizes (though no support for excluding directories as of this writing).
In Powershell, just use Where-Object and exclude the folder names you want (put a * wildcard on the front since it can be difficult to know how many spaces and special characters are on the same line as the folder name):
tree /A | Where-Object {$_ -notlike "*node_modules"} > tree.txt
Edit: This won't exclude subfolders though, it will only exclude the folders you name in the Where-Object clause.
This isn't exactly a complete answer, but should allow you to accomplish what you want with a little work. If you used the Show-Tree code from the PowerShell Community Extensions as a base, and then added in something to filter out folders, you could accomplish what you desire. This is something that should be totally doable, really, since this little bit of code shows how to define a string to exclude (wildcards accepted in a -like format), then get a recursive folder structure and denote if that is an excluded folder, or is contained within an excluded folder.
$DirExcludes = #()
$ToExclude = 'temp*'
GCI -Recurse -Directory |%{
Switch($_){
{$_.Name -ilike $ToExclude} {
$DirExcludes += $_.FullName;
Write-Host $_.FullName -ForegroundColor Red
Continue}
{$DirExcludes -and $_.FullName -match "^($(($DirExcludes|%{[regex]::Escape($_)}) -join '|'))"} {
Write-Host $_.FullName -ForegroundColor DarkRed
Continue
}
default {Write-Host $_.FullName -ForegroundColor Blue}
}
}
When I ran this against my user profile it showed that it caught both a 'Temp' folder and a 'Template' folder, and marked those and each subfolder of those to be excluded. You should be able to get the code of the Show-Tree command by doing:
Get-Command Show-Tree -ShowCommandInfo | % Definition
Then you would just need to devise how to incorporate something similar to what I have above into that code, and you can make a new function to accomplish what you want.
Related
All my movies has a separate folder with a single video file in it. I want all files in a single folder. Accept for the ones with multiple parts (eg. tv series)
I have this powershell script that when $commit=false verboses all the filenames nicely in the list but does not execute the move and delete. I cannot get it to work.
Move videofile from subDir to parentDir
Delete subDir
$commit = $true
$extensions = '.mp4','.avi','.mpg','.mpeg','.mkv','.3gp','.wmv'
$path = 'L:\My Drive\Film\Test'
$folders = gci $path -Directory
foreach($fo in $folders) {
$file = #($fo | gci -File | ? { $_.Extension -in $extensions })
if($file.Count -eq 1)
{
if($commit)
{
[void](mi -Path $file.FullName "$($fo.Parent.FullName)\$($file.Name)")
[void](ri -Path $fo.FullName -Recurse)
}
else
{
Write-Host "$(Get-Date) | Moving '$($file.FullName)' to '$($fo.Parent.FullName)' and deleting folder '$($fo.FullName)'"
}
}
}
Copying the documentation for the next reader since the issue was in path vs literalpath.
https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/supporting-wildcard-characters-in-cmdlet-parameters?view=powershell-7.1
Many Windows PowerShell cmdlets support wildcard characters for their
parameter values. For example, almost every cmdlet that has a Name or
Path parameter supports wildcard characters for these parameters.
(Although most cmdlets that have a Path parameter also have a
LiteralPath parameter that does not support wildcard characters.) The
following command shows how a wildcard character is used to return all
the cmdlets in the current session whose name contains the Get verb.
...
Supported Wildcard Characters * ? [ ]
I was downloading a huge torrent (1.2tb with over 6000 folders) divided in 2 parts, so I placed the 2nd part on the designed place and it was not a problem since the master-folder of the torrent is exactly what was needed. The 1st part master-folder was named with some generic torrent name instead of the name I needed, so instead of renaming the torrent name to "source", which I think would have worked and renamed the currently generic name to "source". In files tab I selected all the files and right-click>relocate all of them and bittorrent simply moved all of the files to the same directory, without any subfolder, and created a mess.
So I have a un-finished backup of this torrent and the files are in place, so my idea was using the un-finished one's name, match with the finished ones and put the finished ones in the un-finished matching name's path folder. I hope that was clear.
I tried to resolve this using PowerShell, but I dont know much, so I came up with this and nothing happens, something is wrong. Anyone knows a solution?
$itemlistA = Get-ChildItem -Path "D:\BitTorrent\" |
ForEach-Object {
$objnameA = $_.Name
$objPathA = $_.FullName
}
$itemlistB = Get-ChildItem -Path "E:\DesiredPath\" -recurse |
ForEach-Object{
$objnameB = $_.Name
$objPathB = $_.FullName
}
ForEach-Object{
if($objnameA -eq $objnameB){
Copy-Item -path $objPathA -Destination $objPathB
Write-Host "ffff Object ($objnameA) new Path ($objPathB) ffff"
}
}
If I'm understanding your intent correctly, the script below will accomplish your goal, assuming your goal is to copy files from a flattened directory into some (potentially) nested directories so that the incoming files overwrite files with matching names.
The O(n^2) performance of the nested loops could be improved with a sort and more efficient search.
You'd need to edit the script's params to reflect your own environment.
param(
$pathToFiles = "$PSScriptRoot\BitTorrent\",
$desiredPath = "$PSScriptRoot\DesiredPath\"
)
$itemlistA = Get-ChildItem -Path $pathToFiles | Select-Object -Property Name, FullName
$itemlistB = Get-ChildItem -Path $desiredPath -Recurse | Select-Object -Property Name, FullName
foreach ($fileA in $itemlistA) {
foreach ($fileB in $itemListB) {
if ($fileB.Name -eq $fileA.Name) {
Copy-Item -path $fileA.FullName -Destination $fileB.FullName -Verbose
break
}
}
}
I'm trying to automate renaming of many multiple files in a Windows 7 directory. I need to search a source index file (.txt or .csv which is a list of extended file names) and, where there is a partial match to the original file name, copy the first 12 characters (of the relevant string in the index file) and rename the original file accordingly (and preserving original file extension).
e.g.
(a) Files currently in the Windows directory are named as follows (hundreds of files):
23456abc.doc
76543cab.doc
92837bca.doc
(b) Values in the .txt/.csv file as follows (hundreds of values - NOTE: these do not have file extensions):
BetterName1.RandomText1.23456abc.MoreRandomText1
BetterName2.RandomText2.76543cab.MoreRandomText2
BetterName3.RandomText3.92837bca.MoreRandomText3
(c) Desired Result is for the files to be auto renamed as follows:
[by searching for filename in (a) within the list of values in (b) and, where there is a match, returning the first 12 characters as the new filename whilst preserving the original file extension]
BetterName1.doc
BetterName2.doc
BetterName3.doc
NOTE: My preference is to use an Index file for the look-up that is in .txt format. However in need I can also use a .csv
I have never used PowerShell before and am new to Windows batch scripting. I have searched around and tried to cobble together snippets of code into a Windows batch script (also tried a PowerShell script) to achieve this but my knowledge in this area is seriously lacking so unfortunately I'm still struggling away at square one.
Any assistance would be greatly appreciated. Thank you in advance.
P.S. Here is a PowerShell script that I tried to get working but to no avail.
$fdPath = 'C:\TEST\Data'
$sourcelistFiles = Get-ChildItem -Path $FDPATH\*.txt | ForEach-Object {$_.user } FullName
$findReplaceList = Import-Csv -Path $FDPATH\AllNames.csv
$totalitems = $sourcelistFiles.count
$currentrow = 0
foreach ($sourcelistFile in $sourcelistFiles)
{
$currentrow += 1
Write-Progress -Activity "Processing record $currentrow of $totalitems" -Status "Progress:" -PercentComplete (($currentrow / $totalitems) * 100)
[string] $txtSourceListFile = Get-Content $sourcelistFile | Out-String
ForEach ($findReplaceItem in $findReplaceList)
{
$txtSourceListFile = $txtSourceListFile -replace "$($findReplaceitem.FindString)", "$($findReplaceitem.ReplaceString)"
}
$txtSourceListFile | Set-Content ($sourcelistFile)
-NoNewLine
}
$FDPATH = 'C:\TEST\Data'
foreach($obj in (Import-Csv -Path $FDPATH\AllNames.csv)){
foreach($thing in $(gci -Path $FDPATH\*.txt)){
if("123.hash.avocado" -match $thing.basename){$ret = $thing.fullname}
}
$stuff = $obj -split "."
ren -Path $ret -NewName $stuff[0]
}
See if this works, it iterates through the csv then iterates through the directory to see if the directory's name is in the csv's line that is being iterated, then sets a variable to be the fullname of the file and renames it to the first name before the period.
Import-CSV LISTA.csv -Header newFileName | % { Copy-Item -Path archivo_convert.pdf -Destination "$($_.newfilename).pdf" }
I have a few thousand files where the author name is contained in the filename of the file. The main problem this creates is that the filename becomes too long as all authors are mentioned and moving them to different folders becomes impossible to due windows filename length limit. I need to rename the files by removing everything after the last occurance of "by " including the "by " itself.
The only way to rename so many files is to write a program of some sort. What would be the quickest way to do this?
You can use a PowerShell script to rename multiple files.
Give this a try:
$cur_dir = pwd
$files = Get-ChildItem $cur_dir
foreach($file in $files){
$rev_name = $file.name
$rev_name = $rev_name.ToCharArray()
[Array]::Reverse($rev_name)
$rev_name = -join $rev_name
$indi = $rev_name.IndexOf("yb")
if($indi -ge 0){
$start_ind = $rev_name.length - $indi-2
$final_name = $file.name.substring(0,$start_ind)
Rename-Item -Path $file.name -NewName $final_name
}
}
I hope it helps.
I have never used Powershell (or VBScript) as I am new to IT-Admin and would appreciate some guidance with renaming files.
On a daily basis, I will be downloading files useing Filezilla manually from a SFTP connection to a local directory filder called YYYYMMDD (although later, I will want to write an automated task that does this form me on a scheduled basis). What I would like to set up is some kind of task that when the files land into the distination directory, they get renamed.
For today's download, I have the following files that I would like to rename:
Original Name Renamed Filename
wcm14444.csv.11.10.20_09.32 wcm14444_111020_0932.csv
wcm14444.csv.11.10.21_00.09 wcm14444_111021_0009.csv
wcm14444.pdf.11.10.20_09.32 wcm14444_111020_0932.pdf
wcm14444.pdf.11.10.21_00.10 wcm14444_111021_0010.pdf
wcm1cash.csv.11.10.21_00.56 wcm1cash_111021_0056.csv
wcm1money.csv.11.10.21_00.56 wcm1money_111021_0056.csv
wcm1opnpos.csv.11.10.21_00.56 wcm1opnpos_111021_0056.csv
wcm1trades.csv.11.10.21_00.56 wcm1trades_111021_0056.csv
wcm1_an.pdf.11.10.21_03.26 wcm1_an_111021_0326.pdf
wcm1_ds.pdf.11.10.21_00.22 wcm1_ds_111021_0022.pdf
wcm1_ep.csv.11.10.21_03.26 wcm1_ep_111021_0326.csv
wcm1_ms.pdf.11.10.21_03.26 wcm1_ms_111021_0326.pdf
You will notice in my renaming requirement:
1. The file extension appears in the middle of the filename and gets placed to the end.
2. I am replacing "." with "" where they appear as date seperators. If its of any help, I am only expecting to receive file types of (".csv", ".pdf" , ".xls") and where these appear within the filename, they get replaced with "_".
Currently I would use Excel to perform the task, but this seems quite hard to deploy as a system task? This seems more of a task for Powershell.
If I am creating YYYYMMDD folders for example N:\SFTP\Provider1\YYYYMMDD, what would be the best way of automating the renaming of the files as the files are downloaded into each day's YYYYMMDD (noting that on same days there may not be a folder created because there are no new files).
Many thanks and kind regards,
Bertie.
Thanks all for the help. More for the benefit of those stumbling accross this page. I have now created a Rename_SFTP.ps1 file at N:\SFTP\Provider1\ containing
$pattern = '^([^\.]+)\.(csv|xls|pdf)\.(\d{2})\.(\d{2})\.(\d{2})_(\d{2})\.(\d{2})'
$todayDate = Get-Date -Format "yyyyMMdd"
Get-ChildItem (".\" + $todayDate + "\") -Recurse | Foreach-Object{
if($_.Name -match $pattern)
{
echo $NewName
$NewName = "{0}_{1}{2}{3}_{4}{5}.{6}" -f $matches[1],$matches[3],$matches[4],$matches[5],$matches[6],$matches[7],$matches[2]
Rename-Item (".\" + $todayDate + "\" + $_) -NewName $NewName
}
}
I have then created a RunRenameTaskForToday.bat containing
powershell .\Rename_SFTP.ps1
At least this way, when I download the files manually, all I need to do is double click on a .bat file one level higher up and it will figure the right folder that needs the files to be renamed. All I need to do know is figure out a way to downlaod the SFTP stuff autonatically...
Thanks all.
Quite a complex task here, with several problems (and, most probably several solutions). Here are some guidelines to get you going:
Use Get-ChildItem | Foreach-Item to traverse all files. The $_ holds the current item.
Powershell has very good support for regular expressions, use if ($_ -match "regexp"). I tend to name the matches, to easier identify them. Something like this could work for your specific naming format:
if ($s -match "(?<pre>.*)\.(?<ext>csv|pdf)(?<date>.*)_(?<hour>\d{2})\.(?<min>\d{2})") {
$newname = $matches["pre"] + "_" +
($matches["date"] -replace "\.","") +
"_" + $matches["hour"] + $matches["min"] +
'.' + $matches["ext"]
}
To get the current day, use Get-Date -Format "yyyyMMdd". Use that to create a new folder each day.
Here's another way using a format string to create the new name. Each group of pattern characters surrounded in parenthesis denotes a part of the file name to be captured and is used later on in the $NewName to form the new file name:
$pattern = '^([^\.]+)\.(csv|xls|pdf)\.(\d{2})\.(\d{2})\.(\d{2})_(\d{2})\.(\d{2})'
Get-ChildItem D:\temp -Recurse | Foreach-Object{
if($_.Name -match $pattern)
{
$NewName = "{0}_{1}{2}{3}_{4}{5}.{6}" -f $matches[1],$matches[3],$matches[4],$matches[5],$matches[6],$matches[7],$matches[2]
Rename-Item $_ -NewName $NewName
}
}
Well quickly something called bricolage ...
$ext = ("csv", "pdf", "xls")
foreach ($e in $ext)
{
Get-ChildItem "*$e*" | % {$newName = $_.name.replace('.',''); $newName;
Rename-Item $_.fullname -NewName ($newName -replace "(.*)($e)(.*)",'$1_$3.$2')}
}