Move every folder to top level including content recursively in Powershell - powershell

how do I recursively move all directories to the top level including all their sub directories.
The files in the directories should be copied as well.
And if a directory already exists its contents should be merged and all the files should remain (maybe via renaming the files)
e.g.
dir1
----img1
----img2
----dir2
--------img1
--------img2
------------dir1
------------img1
------------img2
------------img3
dir4
----img1
----img2
----img3
becomes
dir1
----img1
----img1_1
----img2
----img2_2
----img3
dir2
----img1
----img2
dir4
----img1
----img2
----img3
My approach was something like that.
Get-ChildItem $SOURCE_PATH -Recurse |
Foreach-Object {
$IS_DIR = Test-Path -Path $_.FullName -PathType Container
if ($IS_DIR) {
Move-Item $_.FullName -dest ($DESTPATH + "/" + $_.Name)
}
}
thanks.

Instead of moving directories I would move individual files to have control over destination name of each file.
This code is untested, it is just intended to give you an idea. Tweak as needed.
# Use parameter -File to operate on files only
Get-ChildItem $SOURCE_PATH -File -Recurse | Foreach-Object {
# Get name of parent directory
$parentDirName = $_.Directory.Name
# Make full path of destination directory
$destSubDirPath = Join-Path $DESTPATH $parentDirName
# Create destination directory if not exists (parameter -Force).
# Assignment to $null to avoid unwanted output of New-Item
$null = New-Item $destSubDirPath -ItemType Directory -Force
# Make desired destination file path. First try original name.
$destFilePath = Join-Path $destSubDirPath $_.Name
# If desired name already exists, append a counter until we find an unused name.
$i = 1
while( Test-Path $destFilePath ) {
# Create a new name like "img1_1.png"
$destFilePath = Join-Path $destSubDirPath "$($_.BaseName)_$i.$($_.Extension)"
$i++
}
# Move and possibly rename file.
Move-Item $_ $destFilePath
}

Related

How to print a specific part of directory path in powershell

My script is running in a loop on files and I display the path for each file but the path is too long and I want to display only the relevant directory. the path build always the same
examlpe for output path:
$File.DirectoryName
C:\Users\Bandit\AppData\Local\Temp\9c86ee608bb9477ebb11914c36a5a76d\638110173610123239\IDUDatabase_4.0.0.119\IDUDatabase
I am creating the temp folder and the sub folder as below:
$tempFolder = Join-Path ([IO.Path]::GetTempPath()) (New-GUID).ToString('n')
$subTemp = Join-Path -Path $tempFolder -ChildPath ([datetime]::Now.Ticks)
So this part is constant C:\Users\Bandit\AppData\Local\Temp\9c86ee608bb9477ebb11914c36a5a76d\638110173610123239
I want to display only the 'IDUDatabase_4.0.0.119\IDUDatabase'
The structure is always the same except the user name (C:\User\UserName)
You might use the Resolve-Path -Relative parameter for this:
Set-Location $subtemp
GCI $subtemp -Recurse |Foreach-Object { Resolve-Path -Relative $_.FullName }
.\IDUDatabase_4.0.0.119
.\New folder
.\IDUDatabase_4.0.0.119\IDUDatabase
.\New folder\New Text Document.txt
If you want to remove a prefix from a string you can use the Substring method…
$subTemp = "C:\Users\Bandit\AppData\Local\Temp\9c86ee608bb9477ebb11914c36a5a76d\638110173610123239"
$mypath = Join-Path -Path $subTemp -ChildPath "IDUDatabase_4.0.0.119\IDUDatabase"
$displayPath = $mypath.Substring($subTemp.Length+1)
$displayPath
# IDUDatabase_4.0.0.119/IDUDatabase
Note the +1 is there to remove the path separator immediately after the folder prefix.

Extract a single file from a ZIP archive

I'm using the next code to download some zip archive:
$client = new-object System.Net.WebClient
$client.DownloadFile("https://chromedriver.storage.googleapis.com/$LatestChromeRelease/chromedriver_win32.zip","D:\MyFolder.zip")
As the result I get the ZIP archive "MyFolder.zip" that contains a required file (lets imagine 'test.txt').
How I can extract this particular file from the ZIP archive into a given folder?
PowerShell 4+ has an Expand-Archive command but as of PS 7.2.3 it can only extract the archive completely. So extract it to a temporary directory and copy the file you are interested in.
If you have PS 5.1+ available, scroll down for a more efficient solution that uses .NET classes.
$archivePath = 'D:\MyFolder.zip'
$destinationDir = 'D:\MyFolder'
# Relative path of file in ZIP to extract.
$fileToExtract = 'test.txt'
# Create destination dir if not exist.
$null = New-Item $destinationDir -ItemType Directory -Force
# Create a unique temporary directory
$tempDir = Join-Path ([IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString('n'))
$null = New-Item $tempDir -ItemType Directory
try {
# Extract archive to temp dir
Expand-Archive -LiteralPath $archivePath -DestinationPath $tempDir
# Copy the file we are interested in
$tempFilePath = Join-Path $tempDir $fileToExtract
Copy-Item $tempFilePath $destinationDir
}
finally {
# Remove the temp dir
if( Test-Path $tempDir ) {
Remove-Item $tempDir -Recurse -Force -EA Continue
}
}
With PS 5.1+ you can use .NET classes to directly extract a single file (without having to extract the whole archive):
# Load required .NET assemblies. Not necessary on PS Core 7+.
Add-Type -Assembly System.IO.Compression.FileSystem
$archivePath = 'D:\MyFolder.zip'
$destinationDir = 'D:\MyFolder'
# Relative path of file in ZIP to extract.
# Use FORWARD slashes as directory separator, e. g. 'subdir/test.txt'
$fileToExtract = 'test.txt'
# Create destination dir if not exist.
$null = New-Item $destinationDir -ItemType Directory -Force
# Convert (possibly relative) paths for safe use with .NET APIs
$resolvedArchivePath = Convert-Path -LiteralPath $archivePath
$resolvedDestinationDir = Convert-Path -LiteralPath $destinationDir
$archive = [IO.Compression.ZipFile]::OpenRead( $resolvedArchivePath )
try {
# Locate the desired file in the ZIP archive.
# Replace $_.Fullname by $_.Name if file shall be found in any sub directory.
if( $foundFile = $archive.Entries.Where({ $_.FullName -eq $fileToExtract }, 'First') ) {
# Combine destination dir path and name of file in ZIP
$destinationFile = Join-Path $resolvedDestinationDir $foundFile.Name
# Extract the file.
[IO.Compression.ZipFileExtensions]::ExtractToFile( $foundFile[ 0 ], $destinationFile )
}
else {
Write-Error "File not found in ZIP: $fileToExtract"
}
}
finally {
# Close the archive so the file will be unlocked again.
if( $archive ) {
$archive.Close()
$archive.Dispose()
}
}
Notes:
Convert-Path should be used when passing PowerShell paths that might be relative paths, to .NET APIs. The .NET framework has its own current directory, which doesn't necessarily match PowerShell's. Using Convert-Path we convert to absolute paths so the current directory of .NET is no longer relevant.
.Where and .ForEach are PowerShell intrinsic methods that are available on all objects. They are similar to the Where-Object and ForEach-Object commands but more efficient. Passing 'First' as the 2nd argument to .Where stops searching as soon as we have found the file.
Note that .Where always outputs a collection, even if only a single element matches. This is contrary to Where-Object which returns a single object if only a single element matches. So we have to write $foundFile[ 0 ] when passing it to function ExtractToFile, instead of just $foundFile which would be an array.

Move files to subfolders if they match in Powershell etc?

I've googled for a while now but I'm unable to find any solution for this.
So I have a bunch of files in a folder, and in this folder I have subfolders.
I want to move those files to the subfolders if they match any of these.
Like this:
some random text yellow bananas more text.txt -> \yellow bananas
some other text red apples this is text.txt -> \red apples
Example - files:
Propulsion_mål_2020.jpg
Axevalla Vivid Wise As Goop.jpg
Dagens stjärna Cyber Lane.jpg
640px Elian Web heat.jpg
...
Example - directories:
Propulsion
Vivid Wise As
Cyber Lane
Vitruvio
...
Target:
1st file goes to 1st directory
2nd file goes to 2nd directory
3rd file goes to 3rd directory
4th file match no directory and goes nowhere
Is it doable?
Btw, it's possible that more than one subfolder matches the filename. If so, it doesn't matter which subfolder the file is moved to.
here's one way to do the job ... [grin] there is nearly zero error checking or handling, so you may need to add that. nor is there any record of what was done/not-done.
what it does ...
sets the constants
all one of them. [grin]
creates the files & dirs to work with
when you are ready to work with your own data, remove the entire #region/#endregion block.
gets a list of the dirs in the target location
creates a regex OR of the names of those dirs
gets a list of files in the target dir
iterates thru those files
tests for a match of the .BaseName property of each file against the dir name regex from earlier
if YES, creates a full dir name & moves the file
if NO, writes a warning to the warning stream
that is on by default, so you otta see it when such a file is found.
finishes iterating thru the file list
the code ...
$SourceDir = "$env:TEMP\user3764769"
#region >>> create some files & dirs to work with
# when ready to do this for real, remove this entire block
if (-not (Test-Path -LiteralPath $SourceDir))
{
# the $Null suppresses unwanted "what was done" output
$Null = New-Item -Path $SourceDir -ItemType 'Directory' -ErrorAction 'SilentlyContinue'
}
#'
Propulsion_mal_2020.jpg
Axevalla Vivid Wise As Goop.jpg
Dagens stjarna Cyber Lane.jpg
640px Elian Web heat.jpg
'# -split [System.Environment]::NewLine |
ForEach-Object {
$Null = New-Item -Path $SourceDir -Name $_ -ItemType 'File' -ErrorAction 'SilentlyContinue'
}
#'
Propulsion
Vivid Wise As
Cyber Lane
Vitruvio
'# -split [System.Environment]::NewLine |
ForEach-Object {
$Null = New-Item -Path $SourceDir -Name $_ -ItemType 'Directory' -ErrorAction 'SilentlyContinue'
}
#endregion >>> create some files & dirs to work with
$DirList = Get-ChildItem -LiteralPath $SourceDir -Directory
# the "|" is what regex uses for `-or`
$RegexDL = $DirList.Name -join '|'
$FileList = Get-ChildItem -LiteralPath $SourceDir -File
foreach ($FL_Item in $FileList)
{
# the matched value is stored in $Matches[0]
if ($FL_Item.BaseName -match $RegexDL)
{
$DirName = $Matches[0]
$FullDirName = Join-Path -Path $SourceDir -ChildPath $DirName
Move-Item -LiteralPath $FL_Item.FullName -Destination $FullDirName
}
else
{
Write-Warning ''
Write-Warning ( 'No matching directory was found for [ {0} ].' -f $FL_Item.Name)
Write-Warning ' The file was not moved.'
}
} # end >>> foreach ($FL_Item in $FileList)
output with one file that does not match any dir in the list ...
WARNING:
WARNING: No matching directory was found for [ 640px Elian Web heat.jpg ].
WARNING: The file was not moved.

Need to move a range of numerical files into folders based on filename

We have thousands of packets that are scanned each day into a temporary folder and they are named with their packet number. For example: 301949.pdf, 405311.pdf, 481502.pdf, etc.
Our folder structure is built out into folders at the thousand level and subfolders at the hundred level like this:
Y:\PACKETS\300000\300000-300099
Y:\PACKETS\300000\300100-300199
Y:\PACKETS\300000\300200-300299
Y:\PACKETS\300000\300300-300399
Y:\PACKETS\300000\300400-300499
Y:\PACKETS\300000\300500-300599
Y:\PACKETS\300000\300600-300699
Y:\PACKETS\300000\300700-300799
Y:\PACKETS\300000\300800-300899
Y:\PACKETS\300000\300900-300999
Y:\PACKETS\400000\400000-400099
Y:\PACKETS\400000\400100-400199
Y:\PACKETS\400000\400200-400299
Y:\PACKETS\400000\400300-400399
Y:\PACKETS\400000\400400-400499
Y:\PACKETS\400000\400500-400599
Y:\PACKETS\400000\400600-400699
Y:\PACKETS\400000\400700-400799
Y:\PACKETS\400000\400800-400899
Y:\PACKETS\400000\400900-400999
Y:\PACKETS\481000\481400-481499
Y:\PACKETS\481000\481500-481599
Y:\PACKETS\481000\481600-481699
Y:\PACKETS\481000\481700-481799
Y:\PACKETS\481000\481800-481899
Y:\PACKETS\481000\481900-481999
Y:\PACKETS\481000\481000-481099
Y:\PACKETS\481000\481100-481199
Y:\PACKETS\481000\481200-481299
Y:\PACKETS\481000\481300-481399
We need to move each packet into it's correct folder based on it's numerical filename:
For example:
301949.pdf needs to go into Y:\PACKETS\301000\301900-301999
405311.pdf needs to go into Y:\PACKETS\405000\405300-405399
481502.pdf needs to go into Y:\PACKETS\481000\481500-481599
I'm not even sure how to begin to do this but I'm hoping someone here can help!
I got a bit bored, so here you go:
$SourceFolder = 'Y:\Temp' # the temporary folder where the pdf files are
$Destination = 'Y:\PACKETS' # just the root folder
# you may want to add '-Recurse' if the temp folder contains subfolders
Get-ChildItem -Path $SourceFolder -Filter '*.pdf' -File |
Where-Object { $_.BaseName -match '^\d{4,}$' } | # filenames must be numeric and at least 1000 or more
ForEach-Object {
$packet = [int]$_.BaseName # create a numeric value from the file's BaseName
$level1 = [Math]::Floor($packet / 1000) * 1000 # get the 'thousand' value ( 301949 --> 301000 )
$level2 = [Math]::Floor($packet / 100) * 100 # get the 'hundred' value ( 301949 --> 301900 )
# create the complete path to move the file to
$target = Join-Path -Path $Destination -ChildPath ('{0}\{1}-{2}' -f $level1, $level2, ($level2 + 99))
# test if this path exists and if not create it
if (!(Test-Path -Path $target -PathType Container)) {
$null = New-Item -Path $target -ItemType Directory
}
$_ | Move-Item -Destination $target
}
Hope that helps

Powershell Move file to new destination based on trimmed file name

I have a folder where files get dropped, I wish to pull the files from that folder and move to a new folder based on part of the file name. If the new folder is missing then create it.
I have attempted to put together the below, however it throws an error about the path already existing and doesn't move the file.
File names can be any thing with out pattern except the last 16 characters of the file, I am removing these and using the remaining as the folder name.
I am really new to scripting so if i have made a silly mistake explanations are appreciated.
Edit
I have played with different orders of operations, added a "-Force" to the new item command, tried with using "Else" and not "If (!(".
I am now at the point where it proudly displays the new directory and then stops.
Could i add the move-item part to a new for each loop so it is processed after the dir is created and tested? If so how do you arrange the { } parts?
Edit 2
I finally have it working, updated script below, the movie-item command was having issues when running into special characters in file names, in my case it was square brackets. The -literalpath switch fixed that for me.
Thanks every one for your input.
Updated script 3.0
#Set source folder
$source = "D:\test\source\"
#Set destination folder (up one level of true destination)
$dest = "D:\test\dest\"
#Define filter Arguments
$filter = "*.txt"
<#
$sourcefile - finds all files that match the filter in the source folder
$trimpath - leaves $file as is, but gets just the file name.
$string - gets file name from $trimpath and converts to a string
$trimmedstring - Takes string from $trimfile and removes the last 16 char off the end of the string
Test for path, if it exists then move on, If not then create directory
Move file to new destination
#>
pushd $source
$sourcefile = Get-ChildItem $source -Filter $filter
foreach ($file in $sourcefile){
$trimpath = $file | split-path -leaf
$string = $trimpath.Substring(0)
$trimmedstring = $string.Substring(0,$string.Length-16)
If(!(Test-Path -path "$dest\$trimmedstring")){New-Item "$dest\$trimmedstring" -Type directory -Force}
move-Item -literalpath "$file" "$dest\$trimmedstring"
}
You may have to tweak the paths being used but the below should work.
$sourcefiles = ((Get-ChildItem $source -Filter $filter).BaseName).TrimEnd(16)
foreach ($file in $sourcefiles)
{
if(!(Test-Path "$dest\$file")){
New-item -ItemType directory -path "$dest\$file"
}
Move-Item "$source\$file" "$dest\file"
}
I finally have it working, updated script below, the movie-item command was having issues when running into special characters in file names, in my case it was square brackets. The -literalpath switch fixed that for me. Thanks every one for your input.
#Set source folder
$source = "D:\test\source\"
#Set destination folder (up one level of true destination)
$dest = "D:\test\dest\"
#Define filter Arguments
$filter = "*.txt"
<#
$sourcefile - finds all files that match the filter in the source folder
$trimpath - leaves $file as is, but gets just the file name.
$string - gets file name from $trimpath and converts to a string
$trimmedstring - Takes string from $trimfile and removes the last 16 char off the end of the string
Test for path, if it exists then move on, If not then create directory
Move file to new destination
#>
pushd $source
$sourcefile = Get-ChildItem $source -Filter $filter
foreach ($file in $sourcefile){
$trimpath = $file | split-path -leaf
$string = $trimpath.Substring(0)
$trimmedstring = $string.Substring(0,$string.Length-16)
If(!(Test-Path -path "$dest\$trimmedstring")){New-Item "$dest\$trimmedstring" -Type directory -Force}
move-Item -literalpath "$file" "$dest\$trimmedstring"
}