I´m trying to get a
a) list of all empty folders and subfolders if the folder is named "Archiv"
b) I´d like to delete all those empty folders. My current approch doesn´t check the subfolders.
It would be also great if the results would be exportet in a .csv =)
$TopDir = 'C:\Users\User\Test'
$DirToFind = 'Archiv'>$EmptyDirList = #(
Get-ChildItem -LiteralPath $TopDir -Directory -Recurse |
Where-Object {
#[System.IO.Directory]::GetFileSystemEntries($_.FullName).Count -eq 0
$_.GetFileSystemInfos().Count -eq 0 -and
$_.Name -match $DirToFind
}
).FullName
$EmptyDirList
Any ideas how to adjust the code? Thanks in advance
You need to reverse the order in which Get-ChildItem lists the items so you can remove using the deepest nested empty folder first.
$LogFile = 'C:\Users\User\RemovedEmptyFolders.log'
$TopDir = 'C:\Users\User\Test'
# first get a list of all folders below the $TopDir directory that are named 'Archiv' (FullNames only)
$archiveDirs = (Get-ChildItem -LiteralPath $TopDir -Filter 'Archiv' -Recurse -Directory -Force).FullName |
# sort on the FullName.Length property in Descending order to get 'deepest-nesting-first'
Sort-Object -Property Length -Descending
# next, remove all empty subfolders in each of the $archiveDirs
$removed = foreach ($dir in $archiveDirs) {
(Get-ChildItem -LiteralPath $dir -Directory -Force) |
# sort on the FullName.Length property in Descending order to get 'deepest-nesting-first'
Sort-Object #{Expression = {$_.FullName.Length}} -Descending |
ForEach-Object {
# if this folder is empty, remove it and output its FullName for the log
if (#($_.GetFileSystemInfos()).Count -eq 0) {
$_.FullName
Remove-Item -LiteralPath $_.FullName -Force
}
}
# next remove the 'Archiv' folder that is now possibly empty too
if (#(Get-ChildItem -LiteralPath $dir -Force).Count -eq 0) {
# output this folders fullname and delete
$dir
Remove-Item -LiteralPath $dir -Force
}
}
$removed | Set-Content -Path $LogFile -PassThru # write your log file. -PassThru also writes the output on screen
Not sure a CSV is needed, I think a simple text file will suffice as it's just a list.
Anyway, here's (although not the most elegant) a solution which will also delete "nested empty directories". Meaning if a directory only contains empty directorIS, it will also get deleted
$TopDir = "C:\Test" #Top level directory to scan
$EmptyDirListReport = "C:\EmptyDirList.txt" #Text file location to store a file with the list of deleted directorues
if (Test-Path -Path $EmptyDirListReport -PathType Leaf)
{
Remove-Item -Path $EmptyDirListReport -Force
}
$EmptyDirList = ""
Do
{
$EmptyDirList = Get-ChildItem -Path $TopDir -Recurse | Where-Object -FilterScript { $_.PSIsContainer } | Where-Object -FilterScript { ((Get-ChildItem -Path $_.FullName).Count -eq 0) } | Select-Object -ExpandProperty FullName
if ($EmptyDirList)
{
$EmptyDirList | Out-File -FilePath $EmptyDirListReport -Append
$EmptyDirList | Remove-Item -Force
}
} while ($EmptyDirList)
This should do the trick, should works with nested too.
$result=(Get-ChildItem -Filter "Archiv" -Recurse -Directory $topdir | Sort-Object #{Expression = {$_.FullName.Length}} -Descending | ForEach-Object {
if ((Get-ChildItem -Attributes d,h,a $_.fullname).count -eq 0){
$_
rmdir $_.FullName
}
})
$result | select Fullname |ConvertTo-Csv |Out-File $Logfile
You can do this with a one-liner:
> Get-ChildItem -Recurse dir -filter Archiv |
Where-Object {($_ | Get-ChildItem).count -eq 0} |
Remove-Item
Although, for some reason, if you have nested Archiv files like Archiv/Archiv, you need to run the line several times.
Related
I want to count files for every folder on an E-drive, and output the folder path and file count to a text file using PowerShell (version 2).
I have found this script, but it outputs to console. How do I change it to output to a text file?
Set-Location -Path E:\
Get-ChildItem -recurse | Where-Object{ $_.PSIsContainer } | ForEach-Object{ Write-Host $_.FullName (Get-ChildItem $_.FullName | Measure-Object).Count }
I think it would be best to get an array of resulting objects where you can store both the directory path and the number of files it contains. That way, you can afterwards show it in the console and also save it to a structured CSV file you can open in Excel.
This is for PowerShell 2:
# to keep the property order in PS version < 3.0, create an
# Ordered Dictionary to store the properties first
$dict = New-Object System.Collections.Specialized.OrderedDictionary
# now loop over the folders
$result = Get-ChildItem -Path 'E:\' -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.PSIsContainer } |
ForEach-Object {
# add the results in the temporary ordered dictionary
$dict.Add('Directory', $_.FullName)
$dict.Add('Files', #(Get-ChildItem -Path $_.FullName -Force -ErrorAction SilentlyContinue |
Where-Object { !$_.PSIsContainer }).Count)
# and output a PSObject to be collected in array '$result'
New-Object PSObject -Property $dict
$dict.Clear()
}
# output on screen
$result | Format-Table -AutoSize
#output to CSV file
$result | Export-Csv -Path 'D:\Test\FileCount.csv' -NoTypeInformation
The -Force switch makes sure you also count items that otherwise can't be accessed by the user, such as hidden or system files.
Get-ChildItem c:\tmp -recurse |
Where-Object{ $_.PSIsContainer } |
ForEach-Object {
"$($_.Fullname) $((Get-ChildItem $_.FullName | Where-Object{!$_.PSIsContainer}).count)"
} |
Out-File c:\tmp\out.txt
You can use the > operator for this:
Set-Location -Path E:\
(Get-ChildItem -recurse | Where-Object{ $_.PSIsContainer } | ForEach-Object{ Write-Host $_.FullName (Get-ChildItem $_.FullName | Measure-Object).Count }) >"OUTPUTFILEPATH.txt"
I want to copy a file to multiple destinations using a script that filters through a directory and selects the newest file in the $File_path then change its name and copies it to the $destination, the script i'm using is this:
$File_path = "C:\TEMP\export\liste\Text_Utf8\"
$destination = "C:\TEMP\export\C7E001"
get-childitem -path $File_path -Filter "Ges?*.txt" |
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1 | copy-item -Destination (join-path $destination "FRER3000CCFETES01_IN.DEV")
this only copies it to one location, is there a way to improve it to copy the same file to multiple locations? i have seen this thread but it seems different.
the other locations are as follow:
C:\TEMP\export\C7P001
C:\TEMP\export\C7F001
C:\TEMP\export\C7S001
and so on.
thank you.
Although my answer isn't very different to Peter's answer, This uses the LastWriteTime property to get the latest file and uses the FullName property of the file to copy in the Copy-Item cmdlet.
$File_path = "C:\TEMP\export\liste\Text_Utf8"
$destinations = "C:\TEMP\export\C7E001", "C:\TEMP\export\C7F001", "C:\TEMP\export\C7S001"
$fileToCopy = Get-ChildItem -Path $File_path -Filter "Ges*.txt" -File |
Sort-Object -Property $_.LastWriteTime |
Select-Object -Last 1
foreach ($dest in $destinations) {
Copy-Item -Path $fileToCopy.FullName -Destination (Join-Path -Path $dest -ChildPath "FRER3000CCFETES01_IN.DEV")
}
You can use an foreach object loop
$File_path = "C:\TEMP\export\liste\Text_Utf8\"
$destination = "C:\TEMP\export\C7E001", "C:\TEMP\export\C7P001", "C:\TEMP\export\C7F001", "C:\TEMP\export\C7S001"
$Files = get-childitem -path $File_path -Filter "Ges?*.txt" |
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1
$Destination | Foreach-Object {copy-item $Files -Destination (join-path $_ "FRER3000CCFETES01_IN.DEV")}
I'm having problems to create a PS command that allows me to delete several subfolders without deleting the roof folder.
I.E:
C:\Test has many subfolders:
C:\Test\Item1
C:\Test\Item2
C:\Test\Item3
And the folders Item1, Item2 and Item3 have many subfolders and files.
I would like to create a PS that would allow me to delete all the empty subfolders inside Item1, Item2 and Item3 without deleting Item1, Item2 and Item3 folders. It is possible that any of the Item folders is empty, but I don't want to delete them, just the empty content of each folder.
This is just an example, I have a have around 300 Item folders inside Test.
I usually would use this:
$path="C:\TEST"
do {
$dir = gci $path -directory -recurse | Where { (gci $_.fullName).count -eq 0 } | select -expandproperty FullName
$dir | Foreach-Object { Remove-Item $_ }
} while ($dir.count -gt 0)
But this deletes the folder root folder (Item1, Item2 or Item3) if they are empty.
Thanks in advance.
So you are looking to delete All Items inside Empty Subfolders or all items in General?
This will delete all Folders or Items in General inside of the Directory "C:\abc\"
$path = "C:\abc\"
Get-ChildItem -Path $path -Recurse| Foreach-object {Remove-item -Recurse -path $_.FullName }
This will delete all Folders that dont have any items in them.
$path = "C:\abc\"
Get-ChildItem -Path $path -Recurse | Where-Object {(Get-ChildItem $_.FullName).Count -eq 0} |Foreach-object {Remove-item -Recurse -path $_.FullName }
´
This will look inside "C:\abc\" Get all the children and delete all empty Directories inside the Children in your example this would be Item1,Item2,...
$Path = "C:\abc\"
$itemFolders= Get-ChildItem -Path $Path
$itemFolders| Foreach-Object {
Get-ChildItem -Path $_.FullName |
Where-Object {(Get-ChildItem $_.FullName).Count -eq 0} |
Foreach-object {Remove-item -Recurse -path $_.FullName }
}
Just a quick and dirty bit of Code as I dont have much time, hope I could be of help.
EDIT: Here is what i came up with, its not as performant as I'd like but it gets the job done and is fairly quick, try it out for yourself it worked for me - even threw in a couple of comments and output to clarify what's going on.
$Path="C:\abc\"
$itemFolders = Get-ChildItem $Path
#Get All Folders inside
$AllFolders = $itemFolders | Get-ChildItem -Recurse | Where-Object {$_.PSIsContainer} | Select -Property FullName
#First delete all files older than 30 days
$itemFolders | Get-ChildItem -Recurse -File | ForEach-Object{
$limit = (Get-Date).AddDays(-30)
if($_.LastWriteTime -lt $limit)
{
"{0} hasn't been modified in the last 30 days deleting it" -f $_.FullName
Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue
}
}
#Check if there are files inside that are not containers
$AllFolders | ForEach-Object{
$files = Get-ChildItem -File -Recurse -Path $_.FullName
$directories = Get-ChildItem -Directory -Recurse -Path $_.FullName
#If There are any files inside the folder dont delete it.
if($files.Count -gt 0)
{
"Found {0} files inside {1} do not delete this" -f $files.Count, $_.FullName
}
#If There are no files and no directories inside delete it.
elseif($files.Count -eq 0 -and $directories.Count -eq 0)
{
"Empty Folder {0} deleting it" -f $_.FullName
Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
}
#If there are no files and empty directories inside delete it.
elseif($files.Count -eq 0 -and $directories.Count -gt 0)
{
"No Files but directories found in {0} since its recursive delete it" -f $_.FullName
Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
}
}
I created ps script to copy only files in the folder structure- recursive
cp $source.Text -Recurse -Container:$false -destination $destination.Text
$dirs = gci $destination.Text -directory -recurse | Where { (gci $_.fullName).count -eq 0 } | select -expandproperty FullName
$dirs | Foreach-Object { Remove-Item $_ }
it is working fine. but the problem i have files in the same names. it is not copying duplicated files. i need to rename if file already exist
source:
folderA--> xxx.txt,yyy.txt,
folderB-->xxx.txt,yyy.txt,zzz.txt,
folderc-->xxx.txt
destination (requirement)
xxx.txt
xxx1.txt
xxx2.txt
yyy.txt
yyy1.txt
zzz.txt
Here a solution where I use the Group-Object cmdlet to group all items by the filename. I then iterate over each group and if the group contains more then one file, I append _$ito it where $i starts by one and gets incremented:
$source = $source.Text
$destination = $destination.Text
Get-ChildItem $source -File -Recurse | Group-Object Name | ForEach-Object {
if ($_.Count -gt 1) { # rename duplicated files
$_.Group | ForEach-Object -Begin {$i = 1} -Process {
$newFileName = $_.Name -replace '(.*)\.(.*)', "`$1_$i.`$2"
$i++
Copy-Item -Path $_.FullName -Destination (Join-Path $destination $newFileName)
}
}
else # the filename is unique, just copy it.
{
$_.Group | Copy-Item -Destination $destination
}
}
Note:
You may change the -File to -Container:$false if your PowerShell version doesn't support it. Also note that the script doesn't look into the destination folder whether a file with the same name already exist.
I trying to make a script which compare two directory ( source, destination) and if there are a difference on destination, copy files from source to destination.
The problem is that I don't know how copy the tree of files too.
Example:
$s = "C:\source\client"
$t = "C:\destination\client"
$target = Get-ChildItem $t -Recurse
$source = get-childitem $s -Recurse
Compare-Object $source $target -Property Name , Length |
Where-Object { $_.SideIndicator -eq '<=' } |
foreach-object -process{
copy-item $_.FullName -destination $t
}
If I have a file in source ( C:\source\client\bin\file.txt) and not in the destination folder, how is the code to copy the file in C:\destination\client\bin\file.txt ?
Thanks.
I am in the process of testing this more. From what i can see the logic of your code is sound.
Compare-Object $source $target -Property Name , Length |
Where-Object { $_.SideIndicator -eq '<=' } | Select-Object -ExpandProperty inputobject |
foreach-object -process{
copy-item $_.FullName -destination $t
}
Once you have the compare done pipe the results after the Where in Select-Object -ExpandProperty inputobject to extract the File item so that you can see the FullName property
copy-item has a -recurse parameter that will let you specify the root of a directory and then copy everything below it
copy-item c:\test d:\test -recurse -force
Edit:
The problem is for repeated tasks you can't stop it from trying to overwrite everything. You can add -force to make it do it, but it is not very efficient.
Alternatively (and probably a better and simpler way to go about this) you could call robocopy with the /mir switch
Thanks for sharing. Here is what I have done with everything I searched to compare MD5 and then copy only newly added and different files.
With [Compare contents of two folders using PowerShell Get-FileHash] from http://almoselhy.azurewebsites.net/2014/12/compare-contents-of-two-folders-using-powershell-get-filehash/
$LeftFolder = "D:\YFMS_Target"
$RightFolder = "D:\YFMS_Copy"
$LeftSideHash = #(Get-ChildItem $LeftFolder -Recurse | Get-FileHash -Algorithm MD5| select #{Label="Path";Expression={$_.Path.Replace($LeftFolder,"")}},Hash)
$RightSideHash = #(Get-ChildItem $RightFolder -Recurse | Get-FileHash -Algorithm MD5| select #{Label="Path";Expression={$_.Path.Replace($RightFolder,"")}},Hash)
robocopy $LeftFolder $RightFolder /e /xf *
Write-Host "robocopy LastExitCode: $LastExitCode"
if ($LastExitCode -gt 7) { exit $LastExitCode } else { $global:LastExitCode = $null }
Compare-Object $LeftSideHash $RightSideHash -Property Path,Hash | Where-Object { $_.SideIndicator -eq '<=' } | foreach { Copy-Item -LiteralPath (Join-Path $LeftFolder $_.Path) -Destination (Join-Path $RightFolder $_.Path) -verbose}