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.
So far I have tried the following script:
$SourceFolder = "D:\WORK\JetLetter\LKY\LKY_jV_004\"
$TargetFolder = "D:\WORK\JetLetter\LKY\LKY_jV_004\Final\"
Get-ChildItem -Path $SourceFolder -Filter *.pdf |
ForEach-Object {
$ChildPath = Join-Path -Path $_.Name.Replace('.pdf','') -ChildPath $_.Name
[System.IO.FileInfo]$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPath
if( -not ( Test-Path -Path $Destination.Directory.FullName )){
New-Item -ItemType Directory -Path $Destination.Directory.FullName
}
Copy-Item -Path $_.FullName -Destination $Destination.FullName
}
This creates a folder for every pdf in the folder.
I need it create a single folder based on the 5 digit in the name and move those files into the new folder.
For example: I could have 10 pdf's that have the number "30565" in them and the new folder should be named "30565"
Here are some file names to explain:
LKY_20974_Pr01_1-5000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
LKY_20974_Pr02_5001-10000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
LKY_20974_Pr03_10001-15000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
I have tried to include an else block to the best answer script and haven't had much success. I did however create a separate script that will archive the files before creating a new file. I just have to run it before the main powershell script.
$SourceDir = 'D:\WORK\JetLetter\LKY\LKY_jV_004_9835'
$DestDir = 'D:\WORK\JetLetter\LKY\#Print_Production_Files'
$ArchiveDir = 'D:\WORK\JetLetter\LKY\#Print_Production_Files\#archive'
$Filter = '*.pdf'
$FileList = Get-ChildItem -LiteralPath $SourceDir -Filter $Filter -File
foreach ($FL_Item in $FileList)
{
# this presumes the target dir number is ALWAYS the 2nd item in the split string
$TargetDir = $FL_Item.BaseName.Split('_')[1]
$FullTargetDir = Join-Path -Path $DestDir -ChildPath $TargetDir
if (Test-Path -LiteralPath $FullTargetDir)
{
# the "$Null =" is to suppress unwanted output about what was done
$null = Move-Item -Path $FullTargetDir -Destination $ArchiveDir -Force
}
}
This has made the files and folders a lot more organized.
i think this does what you want done. [grin] the comments seem adequate, but if you have any questions, please ask.
$SourceDir = 'c:\temp\JetLetter\LKY\LKY_jv_004'
$DestDir = 'c:\temp\JetLetter\LKY\LKY_jv_004\Final'
$Filter = '*.pdf'
#region >>> make the dirs and sample files to work with
# remove the entire "#region/#endregion" block when you are ready to work with real data
# make the dirs
$Null = mkdir -Path $SourceDir, $DestDir -ErrorAction 'SilentlyContinue'
# make the test files
$SampleFiles = #(
'LKY_11111_Pr11_1-11111.pdf'
'LKY_22222_Pr22_2-22222.pdf'
'LKY_22222_Pr22_2222-2222.pdf'
'LKY_33333_Pr33_3-3333.pdf'
'LKY_33333_Pr33_33333-33333.pdf'
'LKY_55555_Pr55_5-5555.pdf'
'LKY_77777_Pr77_7-77777.pdf'
'LKY_77777_Pr77_77777-77777.pdf'
'LKY_99999_Pr99_9-99999.pdf'
)
foreach ($SF_Item in $SampleFiles)
{
# the "$Null =" is to suppress unwanted output about what was done
$Null = New-Item -Path $SourceDir -Name $SF_Item -ItemType 'File' -ErrorAction 'SilentlyContinue'
}
#endregion >>> make the dirs and sample files to work with
$FileList = Get-ChildItem -LiteralPath $SourceDir -Filter $Filter -File
foreach ($FL_Item in $FileList)
{
# this presumes the target dir number is ALWAYS the 2nd item in the split string
$TargetDir = $FL_Item.BaseName.Split('_')[1]
$FullTargetDir = Join-Path -Path $DestDir -ChildPath $TargetDir
if (-not (Test-Path -LiteralPath $FullTargetDir))
{
# the "$Null =" is to suppress unwanted output about what was done
$Null = New-Item -Path $FullTargetDir -ItemType 'Directory'
}
$NewFullFileName = Join-Path -Path $FullTargetDir -ChildPath $FL_Item.Name
# leave the file in the source dir if it already is in the final target dir
# you may want to save the not-copied info to a file for later review
if (-not (Test-Path -LiteralPath $NewFullFileName))
{
# the "Move-Item" cmdlet on win7ps5.1 is wildly unreliable
# so i used copy & then remove
$Null = Copy-Item -LiteralPath $FL_Item.FullName -Destination $NewFullFileName
Remove-Item -LiteralPath $FL_Item.FullName
}
else
{
Write-Warning (' {0} already exists in {1}' -f $FL_Item.Name, $FullTargetDir)
Write-Warning ' The file was not moved.'
Write-Warning ''
}
}
screen output only exists for "not moved" files. again, you may want to save the list to a $Var or to a file for later work.
one of the moved files ...
C:\Temp\JetLetter\LKY\LKY_jv_004\Final\22222\LKY_22222_Pr22_2222-2222.pdf
Inside a specific folder I have a few sub-folders, in each are stored image files.
I would like to copy the first image file from each sub-folder into the parent and rename it to the folder's name that it belongs.
I managed to write the following script with the information from several other questions on the site but there is something that is not working as expected. Running the script doesn't copy/rename any file.
$Root = (Get-Item -Path '.\' -Verbose).FullName #'
$Folders = Get-ChildItem -Path $Root -Directory
$Image = Get-ChildItem -Name -Filter *.* | Select-Object -First 1
Foreach($Fld in $Folders)
{
Copy-Item -Path "$($Fld.FullName)\$Image" -Destination "$Root\$($Fld.Name).jpeg"
}
Read-Host -Prompt "Press Enter to exit"
I want to be able to run the script from any folder, the paths must be relative and not absolute/hardcoded. I think the $Root variable achieves that purpose.
The sub-folders only contain image files, the filter *.* in the $Image Get-ChildItem is fine for the purpose as it will always pick an image. However the Copy-Item command will copy it with the jpeg extension, is it possible to check the image file extension and copy/rename accordingly? Perhaps with some If statements?
You're mistakenly getting the $image in your $root-directory since you are using the get-childitem without any -Path parameter. For your purpose you need Foreach $Fld (folder) seperately:
$Root = (Get-Item -Path '.\' -Verbose).FullName #'
$Folders = Get-ChildItem -Path $Root -Directory
Foreach($Fld in $Folders)
{
$Image = Get-ChildItem -Path $Fld -Name -Filter *.* | Select-Object -First 1
Copy-Item -Path "$($Fld.FullName)\$Image" -Destination "$Root\$($Fld.Name).jpeg"
}
Read-Host -Prompt "Press Enter to exit"
Here is you code a little shortened:
$Folders = Get-ChildItem -Directory # Without -path you are in the current working directory
Foreach($Fld in $Folders)
{
$Image = Get-ChildItem -Path $Fld -Filter *.* | Select-Object -First 1 # Without the -name you get the whole fileinfo
Copy-Item -Path $Image.FullName -Destination "$PWD\$($Fld.Name)$($Image.Extension)" # $PWD is a systemvariable for the current working directory
}
Read-Host -Prompt "Press Enter to exit"
You could be even bolder as the FullName of the folder contains the path:
Copy-Item -Path $Image.FullName -Destination "$($Fld.FullName)$($Image.Extension)"
I am trying to copy a file off a server and onto another, I want to keep the structure of the file like so C:\folder\folder\file! If the folder is there copy the file into it, if it is not then create the folders and then copy into it!
I would like it also to filter out the files that are still needed so I want to keep files for 30 days and then move them!
Blockquote
`[int]$Count = 0
$filter = (Get-Date).AddDays(-15).ToString("MM/dd/yyyy")
Get-WMIObject Win32_LogicalDisk | ForEach-Object{
$SearchFolders = Get-Childitem ($_.DeviceID + "\crams") -recurse
$FileList = $SearchFolders |
Where-Object {$_.name -like "Stdout_*" -and $_.lastwritetime -le $filter}
[int]$Totalfiles = ($FileList | Measure-object).count
write-host "There are a total of $Totalfiles found."
echo $FileList
start-sleep 30
[int]
ForEach ($Item in $FileList)
{$Count++
$File = $Item
Write-Host "Now Moving $File"
$destination ="C:\StdLogFiles\"
$path = test-Path (get-childitem $destination -Exclude "Stdout_*")
if ($path -eq $true) {
write-Host "Directory Already exists"
copy-item $File -destination $destination
}
elseif ($path -eq $false) {
cd $destination
mkdir $File
copy-Item $File -destination $destination
}
}
}`
Is what I have so far it has changed a lot due to trying to get it to work but the search works and so does the date part I can not get it to keep the structure of the file!
Okay I took out the bottom part and put in
ForEach ($Item in Get-ChildItem $FileList)
also tried get-content but path is null
{$Count++
$destination = "C:\StdLogFiles"
$File = $Item
Write-Host "Now Moving $File to $destination"
Copy-Item -Path $file.fullname -Destination $destination -force}}
it is copying everything that is in c into that folder but not the files I do not understand what it is doing now! I had it copying the files even wen back to an older version and can't get it to work again! I am going to leave it before I break it more!
Any help or thoughts would be appreciated
I think RoboCopy is probably a simpler solution for you to be honest. But, if you insist on using PowerShell you are going to need to setup your destination better if you want to keep your file structure. You also want to leave your filter date as a [DateTime] object instead of converting it to a string since what you are comparing it to (lastwritetime) is a [DateTime] object. You'll need to do something like:
$filter = (Get-Date).AddDays(-15)
$FileList = Get-WMIObject Win32_LogicalDisk | ForEach-Object{
Get-Childitem ($_.DeviceID + "\crams") -recurse | Where-Object {$_.name -like "Stdout_*" -and $_.lastwritetime -le $filter}
}
$Totalfiles = $FileList.count
For($i = 1;$i -le $TotalFiles; $i++)
{
$File = $FileList[($i-1)]
Write-Progress -Activity "Backing up old files" -CurrentOperation ("Copying file: " + $file.Name) -Status "$i of $Totalfiles files" -PercentComplete ($i*100/$Totalfiles)
$Destination = (Split-Path $file.fullname) -replace "^.*?\\crams", "C:\StdLogFiles"
If(!(Test-Path $Destination)){
New-Item -Path $Destination -ItemType Directory | Out-Null
}
Copy-Item $File -Destination $Destination
}
Write-Progress -Completed
That gathers all the files you need to move from all disks. Takes a count of them, and then enters a loop that will cycle as many times as you have files. In the loop is assigns the current item to a variable, then updates a progress bar based on progress. It then parses the destination by replacing the beginning of the file's full path (minus file name) with your target destination of 'C:\StdLogFiles'. So D:\Crams\HolyPregnantNunsBatman\Stdout04122015.log becomes C:\StdLogFiles\HolyPregnantNunsBatman. Then it tests the path, and if it's not valid it creates it (piped to out-null to avoid spam). Then we copy the file to the destination and move on to the next item. After the files are done we close out the progress bar.
I have a pretty basic powershell copy script that copies items from a source folder to a destination folder. However this is moving way too much data, and I'd like to check if the filename already exists so that file can be ignored. I don't need this as complex as verifying created date/checksum/etc.
Currently it's along the lines of:
Copy-Item source destination -recurse
Copy-Item source2 destination2 -recurse
I'd imagine I need to add the Test-Path cmdlet, but I'm uncertain how to implement it.
You could always call ROBOCOPY from PowerShell for this.
Use the /xc (exclude changed) /xn (exclude newer) and /xo (exclude older) flags:
robocopy /xc /xn /xo source destination
This will ONLY copy those files that are not in the destination folder.
For more option type robocopy /?
$exclude = Get-ChildItem -recurse $dest
Copy-Item -Recurse $file $dest -Verbose -Exclude $exclude
While I agree that Robocopy is the best tool for something like this, I'm all for giving the customer what they asked for and it was an interesting PowerShell exercise.
This script should do just what you asked for: copy a file from Source to Destination only if it does not already exist in the Destination with a minimum of frills. Since you had the -recurse option in your example, that made for a bit more coding than just simply testing for the filename in the Destination folder.
$Source = "C:\SourceFolder"
$Destination = "C:\DestinationFolder"
Get-ChildItem $Source -Recurse | ForEach {
$ModifiedDestination = $($_.FullName).Replace("$Source","$Destination")
If ((Test-Path $ModifiedDestination) -eq $False) {
Copy-Item $_.FullName $ModifiedDestination
}
}
Building off of Wai Ha Lee's post, here's an example that worked for me:
$Source = "<your path here>"
$Dest = "<your path here>"
$Exclude = Get-ChildItem -recurse $Dest
Get-ChildItem $Source -Recurse -Filter "*.pdf" | Copy-Item -Destination $Dest -Verbose -Exclude $Exclude
This builds a list to exclude, then copies any pdf in the source directory and sub-directories to the destination in a single folder...excluding the existing files. Again, this is an example from my needs, but similar to yours. Should be easy enough to tweak to your hearts content.
Function Copy-IfNotPresent will accept one file at a time but it's easy to loop for all files you want to copy. Here's an example:
gci c:\temp\1\*.* -Recurse -File | % { Copy-IfNotPresent -FilePath $_ -Destination "C:\temp\2\$(Resolve-Path $_ -relative)" -Verbose }
Here's the function. It will generate the folder tree if necessary. Here's the gists link: https://gist.github.com/pollusb/cd47b4afeda8edbf8943a8808c880eb8
Function Copy-IfNotPresent {
<#
Copy file only if not present at destination.
This is a one file at a time call. It's not meant to replace complex call like ROBOCOPY.
Destination can be a file or folder. If it's a folder, you can use -Container to force Folder creation when not exists
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory)]
$FilePath,
[Parameter(Mandatory)]
[string]$Destination,
[switch]$Container,
[switch]$WhatIf
)
#region validations
if ($FilePath -isnot [System.IO.FileInfo]){
$File = Get-ChildItem $FilePath -File
} else {
$File = $FilePath
}
if (!$File.Count){
Write-Warning "$FilePath no file found."
return
} elseif ($File.Count -gt 1) {
Write-Warning "$FilePath must resolve to one file only."
return
}
#endregion
# Destination is a folder
if ($Container -or (Test-Path -Path $Destination -PathType Container)) {
if (!(Test-Path $Destination)) {
New-Item -Path $Destination -ItemType Container | Out-Null
}
$Destination += "\$($File.Name)"
}
# Destination is a file
if (!(Test-Path $Destination)) {
if ($WhatIf) {
Write-Host "WhatIf:Copy-IfNotPresent $FilePath -> $Destination"
} else {
# Force creation of parent folder
$Parent = Split-Path $Destination -Parent
if (!(Test-Path $Parent)) {
New-Item $Parent -ItemType Container | Out-Null
}
Copy-Item -Path $FilePath -Destination $Destination
Write-Verbose "Copy-IfNotPresent $FilePath -> $Destination (is absent) copying"
}
} else {
Write-Verbose "Copy-IfNotPresent $Destination (is present) not copying"
}
}
$source = "c:\source"
$destination = "c:\destination"
Create a list of files to exclude, i.e. files already existing in the destination.
$exclude = Get-Childitem -Recurse $destination | ForEach-Object { $_.FullName -replace [Regex]::Escape($destination ), "" }
Recursively copy all contents from the source to the destination excluding the previously collected files.
Copy-Item -Recurse -Path (Join-Path $source "*") -Destination $destination -Exclude $exclude -Force -Verbose
(Join-Path $source "*") add a wildcard at end ensuring that you get the children of the source folder instead of the source folder itself.
Force is used because I don't mind that there are already existing folders (results in error messages). Use with caution.
ForEach-Object { $_.FullName -replace [Regex]::Escape($destination ), "" } transforms the existing file full names into values which can be used as Exclude parameter
Here is a recursive script that syncronizes 2 folders ignoring existing files:
function Copy-FilesAndFolders([string]$folderFrom, [string]$folderTo) {
$itensFrom = Get-ChildItem $folderFrom
foreach ($i in $itensFrom)
{
if ($i.PSIsContainer)
{
$subFolderFrom = $folderFrom + "\" + $i.BaseName
$subFolderTo = $folderTo + "\" + $i.BaseName
Copy-FilesAndFolders $subFolderFrom $subFolderTo | Out-Null
}
else
{
$from = $folderFrom + "\" + $i.Name
$to = $folderTo + "\" + $i.Name
if (!(Test-Path $from)) # only copies non-existing files
{
if (!(Test-Path $folderTo)) # if folder doesn't exist, creates it
{
New-Item -ItemType "directory" -Path $folderTo
}
Copy-Item $from $folderTo
}
}
}
}
To call it:
Copy-FilesAndFolders "C:\FromFolder" "C:\ToFolder"
Lots of great answers in here, here's my contribution as it relates to keeping an mp3 player in sync with a music library.
#Tom Hubbard, 10-19-2021
#Copy only new music to mp3 player, saves time by only copying items that don't exist on the destination.
#Leaving the hardcoded directories and paths in here, sometimes too much variable substitution is confusing for newer PS users.
#Gets all of the albums in the source directory such as your music library
$albumsInLibrary = gci -Directory -path "C:\users\tom\OneDrive\Music" | select -ExpandProperty Name
#Gets all of the albums of your destination folder, such as your mp3 player
$albumsOnPlayer = gci -Directory -Path "e:\" | select -ExpandProperty name
#For illustration, it will list the differences between the music library and the music player.
Compare-Object -DifferenceObject $albumsInLibrary -ReferenceObject $albumsOnPlayer
#Loop through each album in the library
foreach ($album in $albumsInLibrary)
{
#Check to see if the music player contains this directory from the music library
if ($albumsOnPlayer -notcontains $album)
{
#If the album doesn't exist on the music player, copy it and it's child items from the library to the player
write-host "$album is not on music player, copying to music player" -ForegroundColor Cyan
Copy-Item -path "C:\users\Tom\OneDrive\music\$album" -Recurse -Destination e:\$album
}
}