How to move items excluding certain folder using PowerShell? - powershell

The question is an oversimplification of the real issue.
I have a folder let's call it "ParentFolder". Within this folder are files and subfolders. I want all the files and subfolders moved from the "ParentFolder" except one specific subfolder, let's call it "SpecificChildFolder". For the "SpecificChildFolder" I don't want the folder to be moved but only the files in it.
I can do these two tasks separately. I can either move all the files and folders(including the "SpecificChildFolder) in the "ParentFolder" or I can move files from the "SpecificChildFolder" only (excluding the rest of the files and subfolders in the "ParentFolder").
I want these two tasks to happen simultaneously.
I thought I would accomplish this in two separate functions:
Move everything except "SpecificChildFolder"
Move files from within the "SpecificChildFolder"
The stage# 2 code works. It is Stage# 1 I have issues with.
I have also tried Get-ChildItem $src -ErrorAction SilentlyContinue | Where-Object {$_.Directory.Name -NotLike "*SpecificChildFolder*"} | ForEach-Object{} but this doesn't work either
Secondly, can this is not happen in one line of PowerShell?
I am using PowerShell Core 7.2
Code for Stage 1:
#Sources
$src = "C:\User\Desktop\TEST\ParentFolder\*"
$srcMcaNameChg = "C:\User\Desktop\TEST\ParentFolder"
#Destination
$dest = "C:\Users\harguls\Desktop\TEST\DestinationFolder"
Function MoveFiles{
Param(
[string]$src,
[string]$dest,
[string]$srcNameChange
)
Get-ChildItem $src -Recurse -Exclude 'SpecificChildFolder' -ErrorAction SilentlyContinue | ForEach-Object{
$fileName = $_.Name
# Check for duplicate files
$file = Test-Path -Path $dest\$fileName
Write-Output $file
if($file)
{
"$srcNameChange\$fileName" | Rename-Item -NewName ("Copy_"+$fileName)
}
}
Move-Item -Path $src -Destination $dest -Force
}
MoveFiles -src $src -dest $dest -srcNameChange $srcMcaNameChg

Here is a vague representation of what it seems you're looking to accomplish, hope the inline comments are explanatory.
$source = '\path\to\source\folder'
$destination = '\path\to\destination\folder'
# you can add multiple folders here if you want
$exceptWith = 'foldertoexclude1', 'foldertoexclude2'
# get only the subfolders of `$source` and iterate over them
Get-ChildItem $source -Directory | ForEach-Object {
# if this folder name is in the folders to exclude array
if($_.Name -in $exceptWith) {
# get only the files of this folder and move them to destination
Get-ChildItem $exclude -File | Move-Item -Destination $destination
# if we're here we can proceed with next subfolder
return
}
# if we're here means that the name of the folder was not in `$exceptWith`
# so we move this folder and all it's child folders / files
Move-Item -LiteralPath $_.FullName -Destination $destination -Recurse
}

Related

Moving contents of a folder up one level based on folder name

I have a directory of information that is separated into document numbers so each folder that contains documents starts with DOC-######-NameOfDocument. The thing I am trying to do is create a PowerShell script that will search a directory for any folders with a specified document number and then take the contents of that folder, move it up one level, and then delete the original folder (which should now be empty).
Below is the closest I have gotten to my intended result.
$Path = "filepath"
$Folders = Get-ChildItem -Filter "DOC-#####*" -Recurse -Name -Path $Path
$companyID = "######"
foreach ($Folder in $Folders){
$filepath = $Path + $Folder
$Files = Get-ChildItem -Path $filepath
$imagesourc = $filepath + $companyID
$imageDest = $filepath.Substring(0, $filepath.LastIndexOf('\'))
if (Test-Path -Path $imagesourc){
Copy-Item -Path $imagesourc -Destination $imageDest -Recurse
}
foreach ($File in $Files){
$Parent_Directory = Split-Path -Path $File.FullName
$Destination_Path = $filepath.Substring(0, $filepath.LastIndexOf('\'))
Copy-Item -Path $File.FullName -Destination $Destination_Path -Recurse
if ($null -eq (Get-ChildItem -Path $Parent_Directory)) {
}
}
Remove-Item $filepath -Recurse
}
This does what I need but for whatever reason I can't Devine, it will not work on .HTM files. Most of the files I am moving are .html and .htm files so I need to get it to work with .htm as well. The files with .HTM will not move and the folder won't be deleted either which is good at least.
Try using this:
$ErrorActionPreference = 'Stop'
$fileNumber = '1234'
$initialFolder = 'X:\path\to\folders'
$folders = Get-ChildItem -Path $initialFolder -Filter DOC-$fileNumber* -Force -Directory -Recurse
foreach($folder in $folders)
{
try
{
Move-Item $folder\* -Destination $folder.Parent.FullName
Remove-Item $folder
}
catch [System.IO.IOException]
{
#(
"$_".Trim()
"File FullName: {0}" -f $_.TargetObject
"Destination Folder: {0}" -f $folder.Parent.FullName
) | Out-String | Write-Warning
}
catch
{
Write-Warning $_
}
}
Important Notes:
Move-Item $folder\* will move all folder contents recursively. If there are folders inside $folder, those will also be moved too, if you want to target folders which only have files inside, an if condition should be added before this cmdlet.
Try {...} Catch {...} is there to handle file collision mainly, if a file with a same name already exists in the parent folder, it will let you know and it will not be moved nor will the folder be deleted.
-Filter DOC-$fileNumber* will capture all the folders named with the numbers in $fileNumber however, be careful because it may capture folders which you may not intent to remove.
Example: If you want to get all folders containing the number 1234 (DOC-12345-NameOfDocument, DOC-12346-NameOfDocument, ...) but you don't want to capture DOC-12347-NameOfDocument then you should fine tune the filter. Or you could add the -Exclude parameter.
-Force & -Directory to get hidden folders and to target only folders.

ForEach-Object to look in subfolders

I Have this powershell script “copFiles.ps1” that looks in a txt file "Filestocopy.txt" for a list and copies them to a destination
$source = "C:\Data\Filestocopy.txt"
$destination = "C:\Data\Models"
Get-Content $source | ForEach-Object {copy-item $_ $destination}
It’ll only copy the files if they’re in the same folder as the .ps1 file and it ignores subfolders, how can I get it to look in subfolders of the folder that its in, I gather I need to use the -recurse option but don’t know how to rewrite it so it works.
The .ps1 file is fired by a bat file.
Many thanks
I don't know how fast this will be, but you can give an array as the argument for the -Path parameter of Get-ChildItem add the -Recurse switch to dig out the files in subdirectories and simply pipe them along to Copy-Item. something like:
Get-ChildItem (Get-Content $Source) -Recurse |
Copy-Item -Destination $destination
You may also want to add the -File switch.
Update
Based on your comment I played around with this a a little more:
$source = "C:\Data\Filestocopy.txt"
$Destination = "C:\data\Models"
# Get-ChildItem (Get-Content $Source) -Recurse |
Get-ChildItem (Get-Content $Source) -Recurse -File |
ForEach-Object{
If( $_.Directory.FullName -eq $Destination )
{ # Don't work on files already present in the destination
# when the destination is under the current directory...
Continue
}
$FileNum = $null
$NewName = Join-Path -Path $Destination -ChildPath $_.Name
While( (Test-Path $NewName) )
{
++$FileNum
$NewName = Join-Path -Path $Destination -ChildPath ($_.BaseName + "_" + $FileNum + $_.Extension)
}
Copy-Item $_.FullName -Destination $NewName
}
This will increment the destination file name in cases where a file by that name already exists in the destination. If the destination is under the current directory it will prevent analyzing those files by comparing the path of the file to the destination. Files must have unique names in a given folder so I'm not sure how else it can be handled.

Need to copy single file to all subfolders recursively

Please can someone help me create a powershell or CMD script, as simple as possible (1 line?) that will do the following...
-Take file (c:\test.txt)
-Copy to ALL subfolders within a given folder, including multiple levels deep
eg, c:\test1\1\2\3\ and c:\test2\6\7\8\
-Without overwriting that file if it already exists
-Not changing ANY existing files. Just adding the original txt file if it doesn't exist.
I've tried a bunch of scripts I found online, changing them, but have been unsuccessful. Either it overwrites existing files, or doesn't go multiple levels deep, or skips all the folders between top/bottom levels, or throws errors. I give up.
Thanks
Matt
How about something like this...
$folders = Get-ChildItem -Recurse -Path C:\temp\1 -Directory
$file = "c:\temp\test.txt"
foreach($folder in $folders){
$checkFile = $folder.FullName + "\test.txt"
$testForFile=Test-Path -Path $checkFile
if(!$testForFile){
Copy-Item $file -Destination $folder.FullName
}
}
Not a one-liner, but here you go:
$rootFolder = 'PATH OF FOLDER CONTAINING ALL THE SUBFOLDERS'
$fileToCopy = 'c:\test.txt'
$fileName = [System.IO.Path]::GetFileName($fileToCopy)
Get-ChildItem -Path $rootFolder -Recurse -Directory | ForEach-Object {
if (!(Test-Path -Path (Join-Path -Path $_.FullName -ChildPath $fileName) -PathType Leaf)) {
Copy-Item -Path $fileToCopy -Destination $_.FullName
}
}

Copying Files to Another Folder While Keeping a Single Parent Directory

I'm still new to PowerShell and I'm trying to do something I haven't seen an exact answer for yet. We have one large folder with many subfolders that contain individual audio files (our phone system records and places audio files in folders by date). I want to get certain files from within those folders and move them to a different folder, but I want to create the parent folder that the files came from in the new location. I am not grabbing all files within the folders, only a few. For example:
I want to move a file at C:\CopyFrom\02182019\AudioFile.wav
I have a folder at C:\CopyTo, but I want the destination to be C:\CopyTo\02182019\AudioFile.wav
I want the script to create the folder if it doesn't exist, but copy the files into the folder if it does.
I'm currently using Get-ChildItem -Include to get the files I need and piping it into a foreach loop that I found online. This is as close as I've gotten to what I need, but it copies the ENTIRE folder structure, starting at the root, so my destination ends up being:
C:\CopyTo\CopyFrom\02182019\AudioFile.wav
Here is what I have so far:
Get-ChildItem $SourceFolder -Include $ExtArray -Recurse | forEach {
## Remove the original root folder
$split = $_.Fullname -split '\\'
$DestFile = $split[1..($split.Length - 1)] -join '\'
## Build the new destination file path
$DestFile = "C:\DestinationFolder\$DestFile"
## Copy-Item won't create the folder structure so we have to
## create a blank file and then overwrite it
$null = New-Item -Path $DestFile -Type File -Force
Copy-Item -Path $_.FullName -Destination $DestFile -Force
}
I found the foreach loop in this article. I'm assuming there is more I can add to the $split line that will allow me to shave off the rest of the directory structure, but I'm still pretty new to this and don't understand the syntax well enough.
I know this is kind of convoluted to explain, so please let me know if any clarification is needed.
Try this:
$ExtArray = "keep"
$SourceFolder = "C:\source"
$DestFolder = "C:\dest"
Get-ChildItem $SourceFolder -Recurse -Include "*keep*" | foreach {
$source_dir = $_.DirectoryName
$dest_dir = $_.DirectoryName.replace($SourceFolder, $DestFolder)
if (Test-Path $dest_dir) {
Copy-Item -Path $_ -Destination $dest_dir -Recurse
} else {
New-Item -Path $dest_dir -ItemType "directory" | out-null
Copy-Item -Path $_ -Destination $dest_dir -Recurse
}
}
Here's my attempt at this - I set the top level directory as one variable and the underlying folder structure as another.
$File = "C:\test\IT\testfile.docx"
$Destination = "C:\test2\"
$test2 = "\IT\"
mkdir "$Destination\$test2"
Copy-Item -Path $File -Destination "$Destination\$test2" -recurse
I'm not sure how to implement this into your current code but hopefully it gets the wheels spinning and sends you in the right direction!

Move-Item moves entire source directory instead of specified files

In PowerShell, I'm trying to use Move-Item to move all files containing the string "2017", located in $Source, to $MovieDst
The files that I want to move in $Source are individual .mkv files with the string "2017" in the filename, located at the root of $Source - they are not in a directory.
If I hash out Move-Item and un-hash Write-Host $NoDirMovie, it outputs the following:
ExampleFile.2017.mkv
ExampleFile2.2017.mkv
So, it definitely knows which are the correct files to be referencing. But as soon as I use Move-Item, it moves the entire $source directory into $MovieDst. I can't use -Exclude *COMPLETED_DOWNLOADS* as it includes the very files that I want to move.
So, I'm a bit stuck - It can't move my files, because it FIRST wants to move the entire directory containing them. Is there a way to stop it from moving the entire $Source directory, and JUST move only the contents of $Source that contain the string "2017" ?
$Source = "C:\TV Shows 7\COMPLETED_DOWNLOADS"
$MovieDst = "C:\TV Shows 7\COMPLETED DOWNLOADS_Sorted\MOVIES"
$NoDirMovies = dir $Source *2017*
foreach($NoDirMovie in $NoDirMovies | where {$_ -ne $Source})
{
Move-Item -Path $Source -Destination $MovieDst
#Write-Host $NoDirMovie
}
Why are you storing the files you want in $NoDirMovie, then passing $Source to Move-item?
Surely you want:
foreach($NoDirMovie in $NoDirMovies | where {$_ -ne $Source})
{
Move-Item -Path $NoDirMovie -Destination $MovieDst
#Write-Host $NoDirMovie
}
Edit
That's a simple fix, which I haven't tried. A few things:
dir is an alias for the built-in PowerShell Cmdlet Get-ChildItem
Your loop Where is illogical. $NoDirMovies are files in directory $Source, so they will never equal $Source (assume this was an attempt to not copy directory?)
Worth checking if the directory you are trying to copy to exists.
V2 (I tried this one)
$Source = "C:\TV Shows 7\COMPLETED_DOWNLOADS"
$MovieDst = "C:\TV Shows 7\COMPLETED DOWNLOADS_Sorted\MOVIES"
$filter = "2017"
If(!(Test-Path $MovieDst)){
New-Item -ItemType Directory -Path $MovieDst
}
foreach($file in (Get-ChildItem $Source | Where-object {$_.Name -match $filter})){
Move-Item -Path $file.FullName -Destination $MovieDst
}
IMHO your approach is overcomplicated. Move-Item accepts piped input, so
$Source = "C:\TV Shows 7\COMPLETED_DOWNLOADS"
$MovieDst = "C:\TV Shows 7\COMPLETED DOWNLOADS_Sorted\MOVIES\"
Get-ChildItem -Path $Source -Filter "*2017*.mkv" |Where {!($_.PSIsContainer)}
Move-Item -Destination $MovieDst -WhatIf
If the outpout looks OK, remove the -WhatIf