remove items, except folders in an array - powershell

So, i've been scratching my head for a while now and can't seem to figure it out.
I want to delete files and folders older than 'x' days <-- this works fine
I want to delete empty directories left behind <-- this works fine as well
I also want to have some exceptions: filenames and foldernames. The filename exception works fine, but folders don't. There is something strange though. If i put only 1 name in the array of folders i don't want to delete, it works just fine. But if i put multiple in, it suddenly doesn't work anymore?
I have the idea it might be something simple i'm completely missing
$limit = (Get-Date).AddDays(-120)
$path = "C:\Users\user\Documents\files"
$ExcludedFileNames = #("*file1*", "*file2*")
$ExcludedFolders = #("*folder1*", "*folder2*")
# Delete files older than the $limit.
Get-ChildItem -Path $path -Recurse -Force -exclude $ExcludedFileNames |
Where-Object {($_.FullName -notlike $ExcludedFolders) -and (!$_.PSIsContainer) -and ($_.LastWriteTime -lt $limit) } |
Remove-Item -Force
# Delete any empty directories left behind after deleting the old files.
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
Instead of $.FullName i tried $.Name
Instead of -notlike i tried -notin
I also tried removing the array and put the variables after where-object
I also tried to copy other code from lots of posts but didn't seem to help.

The problem is that -notlike expects a single string as it's right-hand side operand, and so the $ExcludedFolders variable is coerced into the stringvalue "*folder1* *folder2*".
The comparison 'C:\some\path\to\a\folder1\with\a\file.exe' -notlike '*folder1* *folder2*' obviously fails.
You can solve this by using the -notmatch regex operator instead:
$ExcludedFolders = #('folder1', 'folder2') # note that we no longer need the wildcards
# later
... |Where-Object {$_.FullName -notmatch ($ExcludedFolders.ForEach{[regex]::Escape($_)} -join '|') -and (-not $_.PsIsContainer) -and $_.LastWriteTime -lt $limit}
The | is the alternation operator in regex, effectively functioning as an OR

I would use wildcards on the file names to use with the -Exclude parameter, and create a regex string for the foldernames to exclude you can use in the Where-Object clause.
Something like this:
$limit = (Get-Date).AddDays(-120).Date # set to midnight instead of the current time
$path = 'C:\Users\user\Documents\files'
$ExcludedFileNames = '*file1*', '*file2*' # wildcards for the Exclude parameter
$ExcludedFolders = 'folder1','folder2' # can be a partial name, do not use wildcards here
# create a regex string for the folder names to exclude
# each item will be Regex Escaped and joined together with the OR symbol '|'
$FoldersToSkip = ($ExcludedFolders | ForEach-Object { [Regex]::Escape($_) }) -join '|'
# Delete files older than the $limit.
Get-ChildItem -Path $path -File -Recurse -Force -Exclude $ExcludedFileNames |
Where-Object {($_.DirectoryName -notmatch $FoldersToSkip) -and ($_.LastWriteTime -lt $limit) } |
Remove-Item -Force
# Delete any empty directories left behind after deleting the old files.
(Get-ChildItem -Path $path -Recurse -Directory -Force).FullName |
Where-Object { !( Get-ChildItem -Path $_ | Select-Object -First 1 ) } |
Sort-Object -Property Length -Descending |
Remove-Item -Force

Related

Powershell Get-ChildItem Exclude Default Windows Folders

I want to search for files with .2fa extension on remote computers. I can find the files I want, but it takes a long time to get to the second computer because it scans all windows files.
I tried the -exclude and where arguments but they do not work.
Could you please help me? Thanks.
$ServerList = Import-Csv 'C:\PC.CSV'
$result = foreach ($pc in $ServerList.barkod) {
$exclude = '*ProgramData*','*Program Files*','*Program Files (x86)*','*Windows*'.'*winupdate*'
$sourcepath = 'c$'
Get-ChildItem -Path \\$pc\$sourcepath -Recurse | Where-Object { $_.Name -like "*.2fa" } |
where {$_.name -notin $Exclude}
}
$result
I tried
-Exclude $exclude
-where {$_.name -notin $Exclude}
-exclude doesn't work with subdirectories or -filter:
Get-ChildItem -Path \\$pc\$sourcepath\ -exclude $exclude |
get-childitem -recurse -filter *.2fa
Since you are looking for files with a certain extension, use the -Filter parameter.
This will be the fastest option to search for only .2fa files, disregarding all others. (Filter works on the Name property)
If you want to search the C: drive, you are bound to hit Access Denied exceptions and because to exclude a list of foldernames using post-process with a Where-Object clause,
Get-ChildItem will try and search in these folders you need to apend -ErrorAction SilentlyContinue to the command
$exclude = 'ProgramData','Program Files','Program Files (x86)','Windows'.'winupdate'
# create a regex string you can use with the `-notmatch` operator
# each item will be Regex Escaped and joined together with the OR symbol '|'
$excludeThese = ($exclude | ForEach-Object { [Regex]::Escape($_) }) -join '|'
$ServerList = (Import-Csv 'C:\PC.CSV').barkod
$sourcepath = 'c$'
$result = foreach ($pc in $ServerList) {
Get-ChildItem -Path "\\$pc\$sourcepath" -Filter '*.2fa' -File -Recurse -ErrorAction SilentlyContinue |
Where-Object {$_.DirectoryName -notmatch $excludeThese}
}
$result

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.

Excluding multiple items from Get-ChildItem using Powershell version 4

I'm iterating through a directory tree but trying to filter out a number of things.
This is my cobbled together code;
Get-ChildItem -Path $pathName -recurse -Filter index.aspx* -Exclude */stocklist/* | ? {$_.fullname -NotMatch "\\\s*_"} | Where {$_.FullName -notlike "*\assets\*" -or $_.FullName -notlike ".bk"}
Remove the name index.aspx from the returned item.
I want to filter out any file that starts with and underscore.
Exclude anything that contains /stocklist/ in its path.
Exclude anything that contains /assets/ in its path.
And exclude anything that contains .bk in its path.
This is working for everything but for the .bk in it's path. I'm pretty sure it's a syntax error on my part.
Thanks in advance.
You can create a regex string and use -notmatch on the file's .DirectoryName property in a Where-Object clause to exclude the files you don't need:
$excludes = '/stocklist/', '/assets/', '.bk'
# create a regex of the folders to exclude
# each folder will be Regex Escaped and joined together with the OR symbol '|'
$notThese = ($excludes | ForEach-Object { [Regex]::Escape($_) }) -join '|'
Get-ChildItem -Path $pathName -Filter 'index.aspx*' -File -Recurse |
Where-Object{ $_.DirectoryName -notmatch $notThese -and $_.Name -notmatch '^\s*_' }

Get-ChildItem Where-Object -notlike $array - Is there a way to do this?

I have written a script that will recurse a specified folder and do some analysis on the files within it. I need to exclude specified sub-folders in the analysis. This list of exclusions changes dependent on the base folder being analysed. I have the script working using a long pattern like this:
Get-ChildItem -File -Recurse $source_folder |
Where-Object {
$_.FullName -notlike "*\folder_name0\*" -and
$_.FullName -notlike "*\folder_name1\*" -and
$_.FullName -notlike "*\folder_name2\*" -and
$_.FullName -notlike "*\folder_name3\*" -and
$_.FullName -notlike "*\folder_name4\*"
}
but this is not very reusable. I would like to be able to store exception lists in .CSVs and call the exception list I need based on the folder set I am analyzing. What I would like to do is something like:
$exception_list = Import-CSV .\exception_list
Get-ChildItem -File -Recurse $source_folder |
Where-Object {$_.FullName -notlike $exception_list}
but this does not work. I suspect because I can't specify and 'and' or an 'or' between the elements in the array. I did briefly consider trying to create the whole argument on the fly using a foreach($exception in $exception_list){$argument += "$_.FullName -notlike $exception -and"}, but that got silly and complex pretty quickly since you still have to remove the last 'and'.
Is there an efficient way to do this?
this builds an array of partial names to be excluded, and uses that array to build a regex OR for use in a -notmatch test.
$ExcludedDirList = #(
'PSES-'
'vscode'
'Test_'
)
# regex uses the pipe symbol as the logical "OR"
$RegexExcludedDirList = $ExcludedDirList -join '|'
$Results = Get-ChildItem -Path $env:TEMP -File -Recurse |
Where-Object {
$_.DirectoryName -notmatch $RegexExcludedDirList
}
I really like #lee_dailey's pattern of creating the regex. An alternative method could be to use -in or -notin to compare collections.
Using Pester:
It 'Filters correctly' {
$list = #('fileA', 'file1', 'file2', 'file32')
$filter = #('file1', 'file3')
$expected = #('fileA', 'file2', 'file32')
$list | Where-Object { $_ -notin $filter} | should -be $expected
}
Or just plain comparison operators:
$list = #('fileA', 'file1', 'file2', 'file32')
$filter = #('file1', 'file3')
$expected = #('fileA', 'file2', 'file32')
$newlist = $list | Where-Object { $_ -notin $filter}
(Compare-Object $newlist $expected).length -eq 0
> True

Get contents of subfolders containing a string in their name

I want to get all files in subfolders, of the same root folder, that all contain the same string ("foo") in the name of the subfolder(s). Below gives me no error, and no output. I don't know what I'm missing.
Get-ChildItem $rootfolder | where {$_.Attributes -eq 'Directory' -and $_.BaseName -contains 'foo'}) | echo $file
Ultimately, I would like to not just echo their names, but move each file to a target folder.
Thank you.
Here is a solution that includes moving the child files of each folder to a new target folder:
$RootFolder = '.'
$TargetFolder = '.\Test'
Get-ChildItem $RootFolder | Where-Object {$_.PSIsContainer -and $_.BaseName -match 'foo'} |
ForEach-Object { Get-ChildItem $_.FullName |
ForEach-Object { Move-Item $_.FullName $TargetFolder -WhatIf } }
Remove -WhatIf when you are happy it's doing what it should be.
You might need to modify the Get-ChildItem $_.FullName part if you (for example) want to exclude sub-directories of the folders, or if you want to include child items in all subfolders of those paths, but not the folders themselves.
replace
Get-ChildItem $rootfolder | where {$_.Attributes -match 'Directory' -and $_.basename -Match 'foo'}) | echo $file
with
Get-ChildItem $rootfolder | where {($_.Attributes -eq 'Directory') -and ($_.basename -like '*foo*')} | Move-Item $targetPath
your request:
that all contain the same string ("foo")
you have to use the -like comparison operator. Also for exact match I would use -eq (case sensitive version is -ceq) instead of -match since its used for matching substrings and patterns.
workflow:
Gets all the files in directory, sending it through pipe to Where-Object cmdlet where you are filtering based on properties Attributes and Basename. When the filtering is done, its being sent to cmdlet Move-Item.
Adapt the first two vars to your environment.
$rootfolder = 'C:\Test'
$target = 'X:\path\to\whereever'
Get-ChildItem $rootfolder -Filter '*foo*' |
Where {$_.PSiscontainer} |
ForEach-Object {
"Processing folder: {0} " -f $_
Move $_\* -Destination $target
}