Cleaning .NET project folders.

Recently, I've been using Claude.ai, an artificial intelligence trained to help in code development.

Today, during a conversation, we came up with the following PowerShell script that cleans a tree of bin and obj folders if they are associated with a .NET project (.csproj, .vbproj, .fsproj).

Here's the script.

[CmdletBinding(SupportsShouldProcess=$true)]
param(
    [Parameter(Position=0)]
    [string]$RootPath = ".",

    [Parameter()]
    [switch]$Force
)

function Write-ColorOutput($ForegroundColor) {
    $fc = $host.UI.RawUI.ForegroundColor
    $host.UI.RawUI.ForegroundColor = $ForegroundColor
    if ($args) {
        Write-Output $args
    }
    $host.UI.RawUI.ForegroundColor = $fc
}

function Test-IsDotNetProject {
    param (
        [string]$FolderPath
    )
    
    $projectFiles = Get-ChildItem -Path $FolderPath -File -Filter "*.*proj" | 
                   Where-Object { $_.Name -match '\.(cs|fs|vb|sql|vcx)proj$' }
    
    return $projectFiles.Count -gt 0
}

function Get-NuGetPackagesFolder {
    $packagesFolders = @()
    
    # Find all 'packages' folders in the solution directories
    $solutionPackages = Get-ChildItem -Path $RootPath -Directory -Recurse -Filter "packages" |
                       Where-Object { $_.FullName -notlike "*\node_modules\*" }
    if ($solutionPackages) {
        $packagesFolders += $solutionPackages
    }
    
    # Add user-level .nuget folder if it exists
    if (Test-Path (Join-Path $env:USERPROFILE ".nuget\packages")) {
        $packagesFolders += Get-Item (Join-Path $env:USERPROFILE ".nuget\packages")
    }
    
    return $packagesFolders
}

function Get-DevFolders {
    param (
        [string]$Path
    )
    
    $folders = @()
    
    # Find all .vs and .git folders recursively, including hidden ones
    $devFolders = Get-ChildItem -Path $Path -Directory -Force -Recurse | 
                  Where-Object { ($_.Name -like ".vs*" -or $_.Name -eq ".git") -and $_.Attributes -match "Hidden" }
    
    if ($devFolders) {
        $folders += $devFolders
    }
    
    return $folders
}

function Remove-BuildFolders {
    param (
        [string]$Path,
        [bool]$ForceDelete
    )
    
    $absolutePath = Resolve-Path $Path
    Write-Output "Scanning directory : $absolutePath"
    
    try {
        $foldersToDelete = @()
        
        # Get bin/obj folders from .NET projects
        $allBinObjFolders = Get-ChildItem -Path $Path -Include @("bin", "obj") -Directory -Recurse -Force -ErrorAction Stop
        foreach ($folder in $allBinObjFolders) {
            $parentPath = Split-Path -Parent $folder.FullName
            if (Test-IsDotNetProject -FolderPath $parentPath) {
                $foldersToDelete += $folder
            }
            else {
                Write-ColorOutput Yellow "Skipping non-.NET folder : $($folder.FullName)"
            }
        }

        # Add NuGet packages folder if found
        $packagesFolders = Get-NuGetPackagesFolder
        if ($packagesFolders) {
            Write-ColorOutput Magenta "Found NuGet packages folders : "
            foreach ($folder in $packagesFolders) {
                Write-ColorOutput Magenta "  $($folder.FullName)"
                $foldersToDelete += $folder
            }
        }
        
        # Add .vs and .git folders
        $devFolders = Get-DevFolders -Path $Path
        if ($devFolders) {
            Write-ColorOutput Magenta "Found development folders : "
            foreach ($folder in $devFolders) {
                Write-ColorOutput Magenta "  $($folder.FullName)"
                $foldersToDelete += $folder
            }
        }
        
        if ($foldersToDelete.Count -eq 0) {
            Write-ColorOutput Yellow "No folders found to clean in : $Path"
            return
        }
        
        Write-Output "`nFound $($foldersToDelete.Count) folders to clean :"
        
        foreach ($folder in $foldersToDelete) {
            $folderType = switch ($folder.Name) {
                { $_ -in @("bin", "obj") } { ".NET build folder" }
                { $_ -eq "packages" } { "NuGet packages folder" }
                { $_ -like ".vs*" } { "Visual Studio folder" }
                { $_ -eq ".git" } { "Git repository folder" }
                default { "Development folder" }
            }
            Write-ColorOutput Cyan "Found $folderType : $($folder.FullName)"
            
            if ($ForceDelete) {
                if ($PSCmdlet.ShouldProcess($folder.FullName, "Delete folder")) {
                    try {
                        Remove-Item -Path $folder.FullName -Recurse -Force -ErrorAction Stop
                        Write-ColorOutput Green "Successfully deleted : $($folder.FullName)"
                    }
                    catch {
                        Write-ColorOutput Red "Failed to delete : $($folder.FullName)"
                        Write-ColorOutput Red "Error : $($_.Exception.Message)"
                    }
                }
            }
        }

        if (-not $ForceDelete) {
            Write-ColorOutput Yellow "`nTo delete these folders, run the script with -Force parameter"
        }
    }
    catch {
        Write-ColorOutput Red "Error scanning directory : $($_.Exception.Message)"
        exit 1
    }
}

# Script execution starts here
$startTime = Get-Date

if (-not (Test-Path $RootPath)) {
    Write-ColorOutput Red "Error : Path '$RootPath' does not exist!"
    exit 1
}

Write-Output "Starting cleanup of development folders..."
Write-Output "Root path : $RootPath"

if ($Force) {
    Write-ColorOutput Red "WARNING : Force parameter specified - folders will be deleted!"
}

Remove-BuildFolders -Path $RootPath -ForceDelete $Force

$endTime = Get-Date
$duration = $endTime - $startTime

Write-Output "`nOperation completed in $($duration.TotalSeconds) seconds"

Comments