Powershell climb directory tree backwards - powershell

I have script located in C:\projects\bacon\packages\build\run.ps1 and I am trying to locate the solution folder (in this case bacon). Everything I've found shows how to climb forward if you know the folder name. But I don't know the project name, so I need to climb backwards until I find the first containing folder that has a packages or dependencies folder within the given script's path.
The closest function I've found is to use Split-Path $script:MyInvocation.MyCommand.Path to get my script's path and perhaps loop backwards somehow? but I can't find anyway of looping the folders backwards until I find the "packages" or "dependencies" folder.

You can use a combination of Get-Item and Get-ChildItem. Get-Item returns an object that has a Parent property. You can limit Get-ChildItem to just directory objects. You can then use this to trek backwards:
$current = Get-Item .
Write-Host $Current.Parent
do
{
$parent = Get-Item $current.Parent.FullName
$childDirectories = $parent | Get-ChildItem -Directory | ? { $_.Name -in #("dependencies","packages") }
$current = $parent
} until ($childDirectories)
$bacon = $parent.FullName
I should say that the first line $current = Get-Item . will only work as is if the current path for the PowerShell runspace is at the end of the tree you are working with.
In your script, if you are using v3, you can replace the . with $PSScriptRoot.

Related

How can I merge this code so all stages use $RootPath? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 days ago.
Improve this question
I'm not a beginner to scripting with PowerShell and have discovered just how powerful and amazing it truly is. I get confused with some things though so I am here seeking help with a few things with the script that I'm in the process of creating. My script manages a few things. It:
• prompt user to select a directory
• recursively moves files that are many levels deep into the parent folder
• deletes all empty folders after the move
•renames the parent folders by removing periods and other "illegal" characters because the program that uses these files will crash if there are any characters besides numbers or letters.
• renames the files to the parent's name.
• exits when finished
The files don't have a file format extension, they're approx 32 characters long and are alphanumeric.
Unfortunately, the script cannot make it past the first step (moving the files) if it is placed in a directory outside of the one that contains the folders and files. If I place it in the root of the directory containing said files and folders, it works flawlessly. If it is ran in another directory containing other files, it will work with the files and folders there after finishing the 1st step which is set using $RootPath, the rest of the script is not using $RootPath and I need to figure out how to edit the code I have to utilize it.
However no matter what I do, I fail. I know I can just run it from the directory containing the files that need to be moved but I intend to release this on a forum that I frequent and want to make sure it is safe for those that use it. ie: I don't want their system getting messed up through carelessness or ignorance.
Full Disclosure: I'm not good at writing code on my own, I find chunks of code offered in forums, test and if it accomplishes what I need, I tweak it to work with my intended use. Most of this code I found here.
How can I get the last ⅔ of my script to use my $RootPath instead of the script's residing directory? I have tried a few things but end up breaking it's functionally and the thing is, in my mind, I see why it's not working but reading the code is where I have a Patrick-Star-drooling moment. This is when I get overwhelmed and take a break or focus on something else that I do understand. I know I need to make the rest of my code utilize $RootPath that gets set when selecting a directory but I can't figure out how to get it to use it.
Additionally, I would like the final step to append "_1" to the file name when there is a naming conflict. I can't seem to figure out how to get this step to carry over from the first step.
Here is a pastebin link of my script. It is a bit long, I have also pasted the code in case that is preferred.
# You need this to use System.Windows.MessageBox
Add-Type -AssemblyName 'PresentationFramework'
Add-Type -AssemblyName System.Windows.Forms
$continue = 'No'
$caption = "'Bulk File Renamer Script' by MyLegzRwheelz."
$message = "Have you read the ENTIRE disclaimer (from the very top, I know, it is a lot) in the console window along the instructions provided and do you agree that you are responsible for your own negligence and anything that can go wrong IF YOU DO NOT FOLLOW MY INSTRUCTIONS PRECISELY? If so, then click 'Yes' to proceed, 'No' to exit."
$continue = [System.Windows.MessageBox]::Show($message, $caption, 'YesNo');
if ($continue -eq 'Yes') {
$characters = "?!'._" # These are the characters the script finds and removes
$regex = "[$([regex]::Escape($characters))]"
$filesandfolders = Get-ChildItem -recurse | Where-Object {$_.name -match $regex}
$browser = New-Object System.Windows.Forms.FolderBrowserDialog
$null = $browser.ShowDialog()
$RootPath = $browser.SelectedPath
# Get list of parent folders in root path
$ParentFolders = Get-ChildItem -Path $RootPath | Where-Object {$_.PSIsContainer}
# For each parent folder get all files recursively and move to parent, append number to file to avoid collisions
ForEach ($Parent in $ParentFolders) {
Get-ChildItem -Path $Parent.FullName -Recurse | Where-Object {!$_.PSIsContainer -and ($_.DirectoryName -ne $Parent.FullName)} | ForEach-Object {
$FileInc = 1
Do {
If ($FileInc -eq 1) {$MovePath = Join-Path -Path $Parent.FullName -ChildPath $_.Name}
Else {$MovePath = Join-Path -Path $Parent.FullName -ChildPath "$($_.BaseName)($FileInc)$($_.Extension)"}
$FileInc++
}
While (Test-Path -Path $MovePath -PathType Leaf)
Move-Item -Path $_.FullName -Destination $MovePath
}
}
$filesandfolders | Where-Object {$_.PsIscontainer} | foreach {
$New=$_.name -Replace $regex
Rename-Item -path $_.Fullname -newname $New -passthru
}
# For this to work, we need to temporarily append a file extension to the file name
Get-ChildItem -File -Recurse | where-object {!($_.Extension)} | Rename-Item -New {$_.basename+'.ext'}
# Removes alphanumeric subdirectories after moving renamed game into the parent folder
Get-ChildItem -Recurse -Directory | ? { -Not ($_.EnumerateFiles('*',1) | Select-Object -First 1) } | Remove-Item -Recurse
# Recursively searches for the files we renamed to .ext and renames it to the parent folder's name
# ie: "B2080E9FFF47FE2DA382BD55EDFCA2152078AEBD58.ext" becomes "0 day Attack on Earth" and will be
# found in the directory of the same name.
ls -Recurse -Filter *.ext | %{
$name = ([IO.DirectoryInfo](Split-Path $_.FullName -Parent)).Name
Rename-Item -Path $_.FullName -NewName "$($name)"
}
} else {
{Exit}
}
I have tried using $ParentFolders in varying places so that it uses $RootPath as the working directory. I have also tried copy/pasting the "file inc" part in the final step but it is not working. To test this out, create folder, make this your root folder. Within that folder, create additional folders with multiple subfolders and a file with no extension, just create .txt and remove the extension then run the script from the newly created root folder.
Do not run this in a directory with files you care about. This why I am trying to get the rest of the code to use only the directory set at launch. To test it to see if it is working regardless of the scripts location, place the script in another folder then run it. When the explorer dialog pops up (after clicking yes), select this directory. If you place it in the root directory then run it, it works as it should but not in any other director, which is the desired result, to run and work to completion, regardless of the location of the script.
Here is code to add _1 to filename
$filename = "abcdefg.csv"
$lastIndex = $filename.LastIndexOf('.')
$extension = $filename.Substring($lastIndex)
$filename = $filename.Substring(0,$lastIndex)
Write-Host "filename = " $filename ",extension = " $extension
$filename = $filename + "_1" + $extension
$filename

Powershell Script "$dirs"

So I do not have a degree or any formal training in any programming language but my job has had me slowly learn the basics of SQL and I have now been given a new task at work. The previous person in charge of this task ran powershell scripts to combine and rename PDFs. I get the macro level of how this all works. The script sets a loop through the parent directory into all the children directory concatenates the PDFs using PDFtk Server then renames the combined PDFs to child directory they are named in. However, I cannot figure out how to specify the dirs (I think thats the term). As it stands now I can only successfully run the powershell script in the folder in which Console2 is located.
$path = Split-Path -parent $MyInvocation.MyCommand.Definition
Function mergeFiles
{
# Loop through all directories
$dirs = dir $path -Recurse | Where { $_.psIsContainer -eq $true }
$cmd = 'C:\Program Files (x86)\PDFtk Server\bin\pdftk.exe'
$In1 = 'A.pdf'
$In2 = 'B.pdf'
$Out1 = 'C.pdf'
Foreach ($dir In $dirs)
This is the first part of the merge files function. Can someone help me figure out how to identify a specfic "dirs"? (Like if I had the PDF in a folder on my desktop)
The location that is populating the directory it will search based on the location of your script, based on this line:
$path = Split-Path -parent $MyInvocation.MyCommand.Definition
$MyInvocation.MyCommand.Definition is the full path to the running script and the command Split-Path -parent will return the parent directory. You could just change that line to be the the location you want i.e.:
$path = 'C:\Users\JC\Desktop\PDF'
but you probably don't want to hardcode that path. What you want to do is add the path as an input argument to the script. To do that, add the following to the top of your script:
PARAM($path)
Then when you invoke your script, you just pass the path you are interested in:
.\theScript.ps1 C:\Users\JC\Desktop\PDF
You can then get more advanced and specify a default value, for example, if you want the default to be the location the script is run:
PARAM($path = '.')

Copy files in a directory, to folders with same name as file

I have multiple files that I want to copy each file to a folder with same name
For example, the files
orange_file100 , orange_file200 , orange_file300 , apple_file120 , apple_file150
I want to move each file to a folder that contain part of the filename say orange and apple so the result will be
orange\orange_file100
orange\orange_file200
orange\orange_file300
apple\apple_file120
apple\apple_file150
How can I do that through powershell, should I use Get-ChildItem then ForEach{Copy-Item) ?
You can use Get-Childitem with a -File or -Directory to only grab the files or folders in a folder, that way you wont grab a folder and try place it in itself.
For example, the code below will only grab the files in the current directory
Get-Childitem -File
You can then use some regex to split the names so you can get the fruit name e.g.
$String.split('_')[0]
You should insert it into a list or array or something to store it, but now you have a list of files and fruit names.
Now you can loop over the list and start to move or copy the files into the right folder structure
Foreach($file in $FileList){
if($file.name -matches $Fruitname){
if($file.name -notmatch $pwd.path ){
mkdir $file.name
cd $file.name
move-item $file.fullname $pwd
}
}
}
The code above is just a quick attempt. It probably wont work the first time and you should make adjustments to understand what you are doing.
A few notes
$pwd gets the current directory. I'm assuming Get-Childitem returns the list of files in the correct order, so you will get Orange_100, then Orange_200 and so on
Get-Childitem returns a powershell object. The file names can be accessed using $_.name or the full path using $_.fullname
If -matches doesn't work, you can also try -like or -in
I didn't add in the first fruit folder into the code above, but it won't be hard to create
Remember to play around and find whats best for you.

Powershell getfiles.count() to exclude thumbs.db

We have a script running daily that removes old files and directories from an area that people use to transfer data around. Everything works great except for one little section. I want to delete a folder if it's older than 7 days and it's empty. The script always shows 1 file in the folder because of the thumbs.db file. I guess I could check to see if the one file is thumb.db and if so just delete the folder but I'm sure there is a better way.
$location = Get-ChildItem \\dropzone -exclude thumbs.db
foreach ($item in $location) {
other stuff here going deeper into the tree...
if(($item.GetFiles().Count -eq 0) -and ($item.GetDirectories().Count -eq 0)) {
This is where I delete the folder but because the folder always has
the Thumbs.db system file we never get here
}
}
$NumberOfFiles = (gci -Force $dir | ?{$_ -notmatch "thumbs.db"}).count
You can try the get-childitem -exclude option where all files/items in your directory will be
counted except those that end in db:
$location = get-childitem -exclude *.db
It also works out if you specify the file to exclude, in this case thumbs.db
$location = get-childitem -exclude thumb.db
Let me know if this works out.
Ah, I also just noticed something,
$location = get-childitem -exclude *.db
Will only handle .db items in the location directory, if you're going deeper into the tree (say from your GetFiles() and GetDirectories() methods) then you may still find a thumb.db. Hence you'll have to add the exclude option in these methods to ignore thumbs.db.
So, for example in your $item.getFiles() method, if you use get-childitem you will have to specify the -exclude option as well.
Sorry, I should have read your question more closely.
Use this method to provide a exclusion list in the form of a simple text file to exclude specific files or extensions from your count:
$dir = 'C:\YourDirectory'
#Type one filename.ext or *.ext per line in this txt file
$exclude = Get-Content "C:\Somefolder\exclude.txt"
$count = (dir $dir -Exclude $exclude).count
$count

Delete items at given path, and then all parents recursively

I have a path. Could be path to a file, could be path to a directory.
Now I need to delete the file (if it is a path to a file) and then check if there's no more files in the same folder, delete it as well, and then check the parent folder and so on.
if it is a path to a directory, delete directory, and then check if the parent is empty - delete it as well, and then its parent and so on.
This script will remove the top folder in the path including everything under it. the $path variable can point to either a file or directory.
$path = "D:\temp\temp2\file.txt"
$parts = $path.Split([System.IO.Path]::DirectorySeparatorChar)
# The following will remove D:\temp and everything in it
Remove-Item (Join-Path $parts[0] $parts[1]) -Recurse
I guess by combining these possible to build something:
Get-ChildItem
Split-Path $path -parent
Remove-Item
If you haven't already done the job this might help you:
You can use this to find out if the child item is a folder
| ? {$_.PSIsContainer}
and combined with this you can see if it is an empty folder
| ? {$_.GetFiles().Count -eq 0}
Good luck!