Powershell delete all folders except one - powershell

I tried
Get-ChildItem -Path 'C:\temp' -Recurse Select Name | Where {$_ -notlike 'C:\temp\one*'} | sort length -Descending | Remove-Item -force
but it doesn't work
Get-ChildItem : A positional parameter cannot be found that accepts argument 'Name'
What's wrong

You were missing a |
Get-ChildItem -Path 'C:\temp' -Recurse | Select -ExpandProperty FullName | Where {$_ -notlike 'C:\temp\one*'} | Remove-Item -force

Try this with -Exclude (And why sort when deleting files?)
Get-ChildItem -Path 'C:\temp' -Recurse -Exclude 'C:\temp\one*' | Remove-Item -force

Use the function below:
Function Delete-Except
{
$path = ""
$exceptions = #(
#Enter files/folders to omit#
)
try:
Get-ChildItem $source -Exclude $exceptions| Remove-Item $_ -Force -Recurse
catch:
Write-Host "Delete operation failed." - Foregroundcolor Red
Pause
}

Related

Powershell Find all empty folders and subfolders in a given Folder name

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.

Copy same file to multiple destinations

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")}

Powershell find and move file

I have a long list of find and move operations, I want to slim down the script by substituting the following 2 long commands with 2 short variables.
Short variables:
$f = 'Get-ChildItem -Recurse -Filter'
$m = 'Move-Item -Force -Verbose -Destination V:\MSL\_pdf\'
Long commands:
Get-ChildItem -Recurse -Filter GAS*.pdf | Move-Item -Force -Verbose -Destination V:\MSL\_pdf\GAS
Get-ChildItem -Recurse -Filter GCA_00*.pdf | Move-Item -Force -Verbose -Destination V:\MSL\_pdf\GCA\GCA_00
Get-ChildItem -Recurse -Filter GCA_01*.pdf | Move-Item -Force -Verbose -Destination V:\MSL\_pdf\GCA\GCA_01
And this doesn't work:
$f GAS*.pdf | $m`GAS
$f GCA_00*.pdf | $m`GCA\GCA_00
$f GCA_01*.pdf | $m`GCA\GCA_01
As #Lee said in the comment, you should use function:
function GetAndMove($files, $dest)
{
$dest = "V:\MSL\_pdf\$($dest)"
Get-ChildItem -Recurse -Filter $files | Move-Item -Force -Verbose -Destination $dest
}
# Now call the function
GetAndMove "GAS*.pdf" "GAS"
GetAndMove "GCA_00*" "GCA\GCA_00"
GetAndMove "GCA_01*" "GCA\GCA_01"
...

Log deleted files to a text file

I want to know how to log the actions from this script to a text file because I don't know how to do it as the cmdlet Start-Transcript doesn't work for me and I wasn't able to find a solution on the Internet.
The problem is that the Where-Object cmdlet doesn't output anything captured by Get-ChildItem.
Does anybody has a good idea to solve this?
$limit = (Get-Date).AddDays(-30)
$path = Split-Path -Parent $MyInvocation.MyCommand.Definition
Get-ChildItem -Path $path -Recurse -Force | Where-Object {
!$_.PSIsContainer -and
$_.LastWriteTime -lt $limit
} | Remove-Item -Force
Get-ChildItem -Path $path -Recurse -Force | Where-Object {
$_.PSIsContainer -and
(Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object {
!$_.PSIsContainer
}) -eq $null
} | Remove-Item -Force -Recurse
try something like this
$limit = (Get-Date).AddDays(-30)
$path =Split-Path -Parent $MyInvocation.MyCommand.Definition
Get-ChildItem $path -file -recurse -force | where LastWriteTime -lt $limit |
Tee-Object -FilePath "c:\temp\deleted.txt" -Append | Remove-Item
Get-ChildItem $path -directory |
where {(Get-ChildItem $_.FullName -file -Recurse | select -First 1) -eq $null} |
Tee-Object -FilePath "c:\temp\deleted.txt" -Append | Remove-Item
howdy error666,
you can use use a few different methods ...
Tee-Object = fork the stream to a file
-PipelineVariable = accumulate the info in a variable
use a loop = put a log-to-file step in it
put a ForEach-Object in the pipeline
that can both log your info and do the Remove-Item.
the loop is the easiest to understand. [grin] however, if you want to keep it in a pipeline, you could add a ForEach-Object where the Where-Object scriptblock is and put both the filter test and the various actions in that block.
take care,
lee

Suppress automatic confirmation of deletion in powershell

I am trying to write a silent script that deletes files older than 14 days and removes empty folders. The part that deletes files work fine, but the part that deletes folders is popping up a confirmation window no matter what I do to suppress it. Here's my code:
$date=(get-date).AddDays(-14)
$ConfirmPreference="None"
$DebugPreference="SilentlyContinue"
$ErrorActionPreference="SilentlyContinue"
$ProgressPreference="SilentlyContinue"
$VerbosePreference="SilentlyContinue"
$WarningPreference="SilentlyContinue"
$OutputEncoding=[console]::OutputEncoding
function FullNuke ([string] $strPath) {
Get-ChildItem -Path $strPath -Recurse -Force | Where-Object {!$_.PSIsContainer -and $_.LastAccessTime -lt $date} | Remove-Item -Force
#The line below is the one that triggers the confirmation
Get-ChildItem -Path $strPath -Recurse -Force | Where-Object {$_.PSIsContainer -and #(Get-ChildItem -LiteralPath $_.FullName -Recurse -Force | Where-Object {!$_.PSIsContainer}).Length -eq 0} | Remove-Item -Force
}
Most of the answers I have found say to add -Recurse to my final Remove-Item command, but that would be the opposite of what I want. If the folder is empty, I want it removed. If it is not empty, I do not want it removed. I'm not sure why non-empty folders are even being caught in the first place.
UPDATE
After much frustration, I discovered that the second line was processing items in reverse order, thus requiring confirmation. It was also not properly identifying empty folders, which also triggered confirmation. I ended up using the following function.
function FullNuke ([string] $strPath) {
Get-ChildItem -Path $strPath -Recurse -Force | Where-Object {!$_.PSIsContainer} | Where-Object {$_.LastAccessTime -lt $date} | Remove-Item -Force
Get-ChildItem -Path $strPath -Recurse -Force | Where-Object {$_.PSIsContainer} | Where-Object {#(Get-ChildItem -LiteralPath $_.FullName -Recurse -Force).Length -eq 0} | Remove-Item -Force
}
I put it here because while it is a solution (it erases files and folders to my satisfaction), it is not an answer to my posted question.
Remove -Force from first Get-ChildItem and add -Recurse and -Confirm:$false.
This will work:
Get-ChildItem -Path $strPath -Recurse |
Where-Object {$_.PSIsContainer -and
#(Get-ChildItem -LiteralPath $_.FullName -Recurse -Force |
Where-Object {!$_.PSIsContainer}).Length -eq 0} |
Remove-Item -Force -Recurse -Confirm:$false
If you are using v3 or higher powershell client you can use the -directory and -file switches for Get-ChildItem and use this:
function FullNuke ([string] $strPath) {
Get-ChildItem -Path $strPath -Recurse -Force -File | Where-Object {$_.LastAccessTime -lt $date} | Remove-Item -Force
Get-ChildItem -Path $strPath -Recurse -Force -Directory | Where-Object {(gci $_.FullName -File -Recurse -Force).count -eq 0} | Remove-Item -Force -Recurse
}
Yes, I added -Recurse to the folder removal because with my testing no folders with any files in them were being passed to that point, and if it is a folder with nothing but empty folders in it then they all need to go anyway right?