11<# PSScriptInfo
2- .VERSION 2.11
2+ .VERSION 2.12
33.GUID 8f7c3b2a-1d4e-5f6a-9b8c-0d1e2f3a4b5c
44.AUTHOR bivlked
55.COMPANYNAME
1212.REQUIREDSCRIPTS
1313.EXTERNALSCRIPTDEPENDENCIES
1414.RELEASENOTES
15+ v2.12: PS 7.4+ compatibility, improved statistics (Docker/WSL/RecycleBin), ReportOnly accuracy
1516 v2.11: Added timeouts for winget/DISM operations, fixed version display, improved reliability
1617 v2.10: Added auto-update check at startup (checks PSGallery for new version)
1718 v2.9: Fixed PSWindowsUpdate installation hanging (TLS 1.2, timeouts)
2122
2223<#
2324. SYNOPSIS
24- WinClean - Ultimate Windows 11 Maintenance Script v2.11
25+ WinClean - Ultimate Windows 11 Maintenance Script v2.12
2526. DESCRIPTION
2627 Комплексный скрипт для обновления и очистки Windows 11:
2728 - Обновление Windows (включая драйверы)
3536 - Подробный цветной вывод + лог-файл
3637. NOTES
3738 Author: biv
38- Version: 2.11
39+ Version: 2.12
3940 Requires: PowerShell 7.1+, Windows 11, Administrator rights
41+ Changes in 2.12:
42+ - Fixed PS 7.4+ compatibility (removed deprecated -UseBasicParsing)
43+ - Fixed DISM ReportOnly to show /ResetBase and warning
44+ - Fixed AppUpdatesCount to only count successful updates
45+ - Added statistics for Docker, WSL, Recycle Bin, npm cache
4046 Changes in 2.11:
4147 - Fixed version display bugs (banner and log showed v2.9 instead of current version)
4248 - Added timeouts for winget/DISM operations to prevent script hangs
@@ -210,7 +216,7 @@ if (-not $LogPath) {
210216}
211217
212218# Script version (single source of truth for version checking)
213- $script :Version = " 2.11 "
219+ $script :Version = " 2.12 "
214220
215221# Protected paths that should never be deleted
216222$script :ProtectedPaths = @ (
@@ -426,8 +432,9 @@ function Test-PSGalleryConnection {
426432 #>
427433 try {
428434 # Check PSGallery API endpoint (faster than main page)
435+ # Note: -UseBasicParsing removed - it was deprecated in PS 6.0 and removed in PS 7.4+
429436 $response = Invoke-WebRequest - Uri " https://www.powershellgallery.com/api/v2" `
430- - TimeoutSec 10 - UseBasicParsing - ErrorAction Stop
437+ - TimeoutSec 10 - ErrorAction Stop
431438 return $response.StatusCode -eq 200
432439 } catch {
433440 return $false
@@ -765,6 +772,38 @@ function Format-FileSize {
765772 return " $Bytes B"
766773}
767774
775+ function ConvertFrom-HumanReadableSize {
776+ <#
777+ . SYNOPSIS
778+ Converts human-readable size string to bytes (inverse of Format-FileSize)
779+ . EXAMPLE
780+ ConvertFrom-HumanReadableSize "2.5 GB" # Returns 2684354560
781+ ConvertFrom-HumanReadableSize "512MB" # Returns 536870912
782+ #>
783+ param ([string ]$SizeString )
784+
785+ if (-not $SizeString ) { return 0 }
786+
787+ # Handle formats: "2.5 GB", "2.5GB", "512 MB", "100.5MB"
788+ if ($SizeString -match ' ^([\d.,]+)\s*([KMGT]?B)$' ) {
789+ $value = [double ]($Matches [1 ] -replace ' ,' , ' .' )
790+ $unit = $Matches [2 ].ToUpper()
791+
792+ $multiplier = switch ($unit ) {
793+ ' B' { 1 }
794+ ' KB' { 1 KB }
795+ ' MB' { 1 MB }
796+ ' GB' { 1 GB }
797+ ' TB' { 1 TB }
798+ default { 1 }
799+ }
800+
801+ return [long ]($value * $multiplier )
802+ }
803+
804+ return 0
805+ }
806+
768807function Test-PathProtected {
769808 <#
770809 . SYNOPSIS
@@ -1301,11 +1340,11 @@ function Update-Applications {
13011340 return
13021341 }
13031342
1304- $script :Stats.AppUpdatesCount = $updateCount
1305-
13061343 if ($upgradeProcess.ExitCode -eq 0 ) {
1344+ $script :Stats.AppUpdatesCount = $updateCount
13071345 Write-Log " Application updates completed successfully" - Level SUCCESS
13081346 } else {
1347+ # Don't count as successful updates if winget returned an error
13091348 Write-Log " Application updates completed with code: $ ( $upgradeProcess.ExitCode ) " - Level WARNING
13101349 $script :Stats.WarningsCount ++
13111350 }
@@ -1496,22 +1535,68 @@ function Clear-WindowsUpdateCache {
14961535 }
14971536}
14981537
1538+ function Get-RecycleBinSize {
1539+ <#
1540+ . SYNOPSIS
1541+ Gets the total size of items in the Recycle Bin
1542+ #>
1543+ $totalSize = [long ]0
1544+ try {
1545+ $shell = New-Object - ComObject Shell.Application
1546+ $recycleBin = $shell.Namespace (0xA )
1547+ foreach ($item in $recycleBin.Items ()) {
1548+ try {
1549+ # Try ExtendedProperty first (more reliable)
1550+ $itemSize = $item.ExtendedProperty (" System.Size" )
1551+ if ($itemSize ) {
1552+ $totalSize += [long ]$itemSize
1553+ }
1554+ } catch {
1555+ # Ignore errors for individual items
1556+ }
1557+ }
1558+ } catch {
1559+ # Return 0 if we can't access recycle bin
1560+ }
1561+ return $totalSize
1562+ }
1563+
14991564function Clear-WinCleanRecycleBin {
15001565 <#
15011566 . SYNOPSIS
1502- Empties the Recycle Bin
1567+ Empties the Recycle Bin with size tracking
15031568 #>
15041569 Write-Log " Recycle Bin" - Level SECTION
15051570
1571+ # Measure size before cleanup
1572+ $sizeBefore = Get-RecycleBinSize
1573+
15061574 if ($ReportOnly ) {
1507- Write-Log " Would clean: Recycle Bin" - Level DETAIL
1575+ if ($sizeBefore -gt 0 ) {
1576+ Write-Log " Would clean: Recycle Bin - $ ( Format-FileSize $sizeBefore ) " - Level DETAIL
1577+ } else {
1578+ Write-Log " Recycle Bin is empty" - Level DETAIL
1579+ }
1580+ return
1581+ }
1582+
1583+ if ($sizeBefore -eq 0 ) {
1584+ Write-Log " Recycle Bin is already empty" - Level INFO
15081585 return
15091586 }
15101587
15111588 try {
15121589 # Use full cmdlet path to explicitly call the built-in cmdlet
15131590 Microsoft.PowerShell.Management\Clear-RecycleBin - Force - ErrorAction Stop
1514- Write-Log " Recycle Bin emptied" - Level SUCCESS
1591+
1592+ # Update statistics
1593+ $script :Stats.TotalFreedBytes += $sizeBefore
1594+ if (-not $script :Stats.FreedByCategory.ContainsKey (" Recycle Bin" )) {
1595+ $script :Stats.FreedByCategory [" Recycle Bin" ] = 0
1596+ }
1597+ $script :Stats.FreedByCategory [" Recycle Bin" ] += $sizeBefore
1598+
1599+ Write-Log " Recycle Bin emptied - $ ( Format-FileSize $sizeBefore ) " - Level SUCCESS
15151600 } catch {
15161601 # Fallback to COM method
15171602 try {
@@ -1524,7 +1609,14 @@ function Clear-WinCleanRecycleBin {
15241609 Remove-Item - LiteralPath $_.Path - Recurse - Force - ErrorAction SilentlyContinue
15251610 }
15261611
1527- Write-Log " Recycle Bin emptied ($count items)" - Level SUCCESS
1612+ # Update statistics even for fallback method
1613+ $script :Stats.TotalFreedBytes += $sizeBefore
1614+ if (-not $script :Stats.FreedByCategory.ContainsKey (" Recycle Bin" )) {
1615+ $script :Stats.FreedByCategory [" Recycle Bin" ] = 0
1616+ }
1617+ $script :Stats.FreedByCategory [" Recycle Bin" ] += $sizeBefore
1618+
1619+ Write-Log " Recycle Bin emptied ($count items) - $ ( Format-FileSize $sizeBefore ) " - Level SUCCESS
15281620 } catch {
15291621 Write-Log " Could not empty Recycle Bin: $_ " - Level WARNING
15301622 $script :Stats.WarningsCount ++
@@ -1948,8 +2040,21 @@ function Clear-DeveloperCaches {
19482040 $npm = Get-Command npm - ErrorAction SilentlyContinue
19492041 if ($npm ) {
19502042 try {
2043+ $sizeBefore = Get-FolderSize $npmCache
19512044 & npm cache clean -- force 2>&1 | Out-Null
1952- Write-Log " npm cache cleaned (via npm)" - Level SUCCESS
2045+ $sizeAfter = Get-FolderSize $npmCache
2046+ $freed = $sizeBefore - $sizeAfter
2047+
2048+ if ($freed -gt 0 ) {
2049+ $script :Stats.TotalFreedBytes += $freed
2050+ if (-not $script :Stats.FreedByCategory.ContainsKey (" Developer" )) {
2051+ $script :Stats.FreedByCategory [" Developer" ] = 0
2052+ }
2053+ $script :Stats.FreedByCategory [" Developer" ] += $freed
2054+ Write-Log " npm cache cleaned - $ ( Format-FileSize $freed ) " - Level SUCCESS
2055+ } else {
2056+ Write-Log " npm cache cleaned (via npm)" - Level SUCCESS
2057+ }
19532058 } catch {
19542059 Remove-FolderContent - Path $npmCache - Category " Developer" - Description " npm cache"
19552060 }
@@ -2076,9 +2181,20 @@ function Clear-DockerWSL {
20762181 Write-Log " Running docker system prune..." - Level INFO
20772182 $result = docker system prune -f 2>&1
20782183
2079- # Parse reclaimed space
2080- if ($result -match " reclaimed\s+([\d.]+\s*[KMGT]?B)" ) {
2081- Write-Log " Docker cleanup: $ ( $Matches [1 ]) reclaimed" - Level SUCCESS
2184+ # Parse reclaimed space and add to statistics
2185+ if ($result -match " reclaimed\s+([\d.,]+\s*[KMGT]?B)" ) {
2186+ $reclaimedStr = $Matches [1 ]
2187+ $reclaimedBytes = ConvertFrom-HumanReadableSize $reclaimedStr
2188+
2189+ Write-Log " Docker cleanup: $reclaimedStr reclaimed" - Level SUCCESS
2190+
2191+ if ($reclaimedBytes -gt 0 ) {
2192+ $script :Stats.TotalFreedBytes += $reclaimedBytes
2193+ if (-not $script :Stats.FreedByCategory.ContainsKey (" Docker" )) {
2194+ $script :Stats.FreedByCategory [" Docker" ] = 0
2195+ }
2196+ $script :Stats.FreedByCategory [" Docker" ] += $reclaimedBytes
2197+ }
20822198 } else {
20832199 Write-Log " Docker cleanup completed" - Level SUCCESS
20842200 }
@@ -2151,6 +2267,10 @@ exit
21512267 if ($saved -gt 0 ) {
21522268 Write-Log " Compacted $ ( $vhdxFile.Name ) : $ ( Format-FileSize $saved ) saved" - Level SUCCESS
21532269 $script :Stats.TotalFreedBytes += $saved
2270+ if (-not $script :Stats.FreedByCategory.ContainsKey (" WSL" )) {
2271+ $script :Stats.FreedByCategory [" WSL" ] = 0
2272+ }
2273+ $script :Stats.FreedByCategory [" WSL" ] += $saved
21542274 } else {
21552275 Write-Log " Compacted $ ( $vhdxFile.Name ) : no space saved" - Level INFO
21562276 }
@@ -2277,7 +2397,8 @@ function Invoke-DISMCleanup {
22772397 Write-Log " Windows Component Cleanup (DISM)" - Level SECTION
22782398
22792399 if ($ReportOnly ) {
2280- Write-Log " Would run: DISM /Online /Cleanup-Image /StartComponentCleanup" - Level DETAIL
2400+ Write-Log " Would run: DISM /Online /Cleanup-Image /StartComponentCleanup /ResetBase" - Level DETAIL
2401+ Write-Log " Note: /ResetBase removes ability to uninstall updates" - Level WARNING
22812402 return
22822403 }
22832404
0 commit comments