问题
1. Code Description alias how it is intended to work
User enters a path to a directory in PowerShell. Code checks if any folder within the declared directory contains no data at all. If so, the path of any empty folder will be shown on the prompt to the user and eventually removed from the system.
2. The Issue alias what I am struggling with
The code I just wrote doesn't count the depth of a folder hierarchy as I would expect (the column in the output table is blank). Besides that, the program works okay - I've still got to fix the issue where my code removes empty parent directories first and child directories later, which of course will cause an error in PowerShell; for instance, take
C:\Users\JohnMiller\Desktop\Homework
where Homework
consists of Homework\Math\School Project
and Homework\Computer Science\PowerShell Code
. Note that all directories are supposed to be empty with the exception of PowerShell Code
, the folder containing this script. (Side note: A folder is considered empty when no file dwells inside. At least that's what my code is based on for now.)
3. The Code
# Delete all empty (sub)folders in [$path]
[Console]::WriteLine("`n>> Start script for deleting all empty (sub)folders.")
$path = Read-Host -prompt ">> Specify a path"
if (test-path $path)
{
$allFolders = Get-ChildItem $path -recurse | Where {$_.PSisContainer -eq $True}
$allEmptyFolders = $allFolders | Where-Object {$_.GetFiles().Count -eq 0}
$allEmptyFolders | Select-Object FullName,@{Name = "FolderDepth"; Expression = {$_.DirectoryName.Split('\').Count}} | Sort-Object -descending FolderDepth,FullName
[Console]::WriteLine("`n>> Do you want do remove all these directories? Validate with [True] or [False].") #'#
$answer = Read-Host -prompt ">> Answer"
if ([System.Convert]::ToBoolean($answer) -eq $True)
{
$allEmptyFolders | Remove-Item -force -recurse
}
else
{
[Console]::WriteLine(">> Termination confirmed.`n")
exit
}
}
else
{
[Console]::WriteLine(">> ERROR: [$($path)] is an invalid directory. Program terminates.`n")
exit
}
回答1:
The depth-count problem:
Your code references a .DirectoryName
property in the calculated property passed to Select-Object
, but the [System.IO.DirectoryInfo]
instances output by Get-ChildItem
have no such property. Use the .FullName
property instead:
$allEmptyFolders |
Select-Object FullName,@{Name='FolderDepth'; Expression={$_.FullName.Split('\').Count}} |
Sort-Object -descending FolderDepth,FullName
Eliminating nested empty subfolders:
To recap your problem with a simple example:
If c:\foo
is empty (no files) but has empty subdir. c:\foo\bar
, your code outputs them both, and if you then delete c:\foo
first, deleting c:\foo\bar
next fails (because deleting c:\foo
also removed c:\foo\bar
).
If you eliminate all nested empty subdirs. up front, you not only declutter what you present to the user, but you can then safely iterative of the output and delete one by one.
With your approach you'd need a 2nd step to eliminate the nested empty dirs., but here's a depth-first recursive function that omits nested empty folders. To make it behave the same way as your code with respect to hidden files, pass -Force
.
function Get-RecursivelyEmptyDirectories {
[cmdletbinding()]
param(
[string] $LiteralPath = '.',
[switch] $Force,
[switch] $DoNotValidatePath
)
$ErrorActionPreference = 'Stop'
if (-not $DoNotValidatePath) {
$dir = Get-Item -LiteralPath $LiteralPath
if (-not $dir.PSIsContainer) { Throw "Not a directory path: $LiteralPath" }
$LiteralPath = $dir.FullName
}
$haveFiles = [bool] (Get-ChildItem -LiteralPath $LiteralPath -File -Force:$Force | Select-Object -First 1)
$emptyChildDirCount = 0
$emptySubdirs = $null
if ($childDirs = Get-ChildItem -LiteralPath $LiteralPath -Directory -Force:$Force) {
$emptySubDirs = New-Object System.Collections.ArrayList
foreach($childDir in $childDirs) {
if ($childDir.LinkType -eq 'SymbolicLink') {
Write-Verbose "Ignoring symlink: $LiteralPath"
} else {
Write-Verbose "About to recurse on $($childDir.FullName)..."
try { # If .AddRange() fails due to exceeding the array list's capacity, we must fail too.
$emptySubDirs.AddRange(@(Get-RecursivelyEmptyDirectories -DoNotValidatePath -LiteralPath $childDir.FullName -Force:$Force))
} catch {
Throw
}
# If the last entry added is the child dir. at hand, that child dir.
# is by definition itself empty.
if ($emptySubDirs[-1] -eq $childDir.FullName) { ++$emptyChildDirCount }
}
} # foreach ($childDir ...
} # if ($childDirs = ...)
if (-not $haveFiles -and $emptyChildDirCount -eq $childDirs.Count) {
# There are no child files and all child dirs., if any, are themselves
# empty, so we only output the input path at hand, as the highest
# directory in this subtree that is empty (save for empty descendants).
$LiteralPath
} else {
# This directory is not itself empty, so output the (highest-level)
# descendants that are empty.
$emptySubDirs
}
}
Tips regarding your code:
Get-ChildItem -Directory
is available in PSv3+, which is not only shorter but also more efficient thanGet-ChildItem | .. Where { $_.PSisContainer -eq $True }
.Use
Write-Host
instead of[Console]::WriteLine
[System.Convert]::ToBoolean($answer)
only works with the culture-invariant string literals'True'
and'False'
([bool]::TrueString
and[bool]::FalseString
, although case variations and leading and trailing whitespace are allowed).
来源:https://stackoverflow.com/questions/42846045/counting-folder-depth-with-powershell