diff --git a/WslCompact/WslCompact.psd1 b/WslCompact/WslCompact.psd1 new file mode 100644 index 0000000..73226a8 --- /dev/null +++ b/WslCompact/WslCompact.psd1 @@ -0,0 +1,131 @@ +# +# Module manifest for module 'WslCompact' +# +# Generated by: Oscar Lopez +# +# Generated on: 2023-02-02 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'WslCompact.psm1' + +# Version number of this module. +ModuleVersion = '6.0' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = '3169a4cc-3870-4ea2-a4b6-64156c107797' + +# Author of this module +Author = 'Oscar Lopez' + +# Company or vendor of this module +CompanyName = 'Unknown' + +# Copyright statement for this module +Copyright = '(c) Oscar Lopez. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'Compacts the size of the ever-growing WSL images' + +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '5.0' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = 'WslCompact' + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = '*' + +# Variables to export from this module +VariablesToExport = '*' + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = '*' + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + LicenseUri = 'https://github.com/okibcn/wslcompact/blob/main/LICENSE' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/okibcn/wslcompact' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} diff --git a/WslCompact/WslCompact.psm1 b/WslCompact/WslCompact.psm1 new file mode 100644 index 0000000..8fa5c91 --- /dev/null +++ b/WslCompact/WslCompact.psm1 @@ -0,0 +1,127 @@ +# WSL compact v6.0 2023.02.03 +# (C) 2023 Oscar Lopez. +# For more information visit: https://github.com/okibcn/wslcompact +# + +function WslCompact { + $sf = 1.05 + $compact = $false + $data = $false + $force = $false + $help = $false + $target_distros = foreach ($arg in $args) { + if ($arg[0] -eq '-') { + $compact = $compact -or ("Cc" -match $arg[1]) + $data = $data -or ("Dd" -match $arg[1]) + $force = $force -or ("Yy" -match $arg[1]) + $help = $help -or ("Hh" -match $arg[1]) + } + else { + $arg + } + } + Write-Host " WSL compact v6.0 2023.02.03 + (C) 2023 Oscar Lopez + wslcompact -h for help. For more information visit: https://github.com/okibcn/wslcompact" + + if ($help) { + Write-Host " + + Usage: wslcompact [OPTIONS] [DISTROS] + + wslcompact compacts the images of WSL distros by removing unsused space. + If no option is provided, it will default to info mode, without modifying any image. + If no distro is provided it will process all the installed images. + NOTE: WSL will be shutdown for compacting the images. + + Options: + no opt. Provides distro name, path, size, and estimated new size information. + -c Compacting mode: process the selected distros compacting the images. + -y replaces selected images without asking for confirmation. + -d Enable the processing of data images. Default is disabled. + -h Prints this help + + Examples: + wslcompact + wslcompact -c -d + wslcompact -c -y Ubuntu Kali + " + return + } + $wsl_version = (wsl --version)[0].split(' ')[-1] + if ($wsl_version -lt "1.0") { + Write-Host " + WARNING: + your are using wsl version $wsl_version. wslcompact requires WSL version 1.0.0 or higher. + You can update WSL typing: wsl --update in PowerShell or using the Microsoft Store. + +" + return + } + $tmp_folder = "$Env:TEMP\wslcompact" + $freedisk = (Get-PSDrive $env:TEMP[0]).free + mkdir "$tmp_folder" -ErrorAction SilentlyContinue | Out-Null + Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss\`{* | ForEach-Object { + $wsl_ = Get-ItemProperty $_.PSPath + $wsl_distro = $wsl_.DistributionName + $wsl_path = if ($wsl_.BasePath.StartsWith('\\')) { $wsl_.BasePath.Substring(4) } else { $wsl_.BasePath } + if ( !$target_distros -or ($wsl_distro -in $target_distros) ) { + # The wsl_distro is marked for processing + $size1 = (Get-Item -Path "$wsl_path\ext4.vhdx").Length / 1MB + Write-Host "`n Distro's name: $wsl_distro" + Write-Host " Image file: $wsl_path\ext4.vhdx" + Write-Host " Current size: $size1 MB" + if ("$wsl_distro" -match "data") { + Write-Host " The image is not a WSL OS, but a data partition. No size estimation is available at this time." + $estimated = [long]($size1) + } + else { + $estimated = ((wsl --system -d "$wsl_distro" -e df /mnt/wslg/distro)[1] -split '\s+')[2] + $estimated = [long]($estimated / 1024) + Write-Host " Estimated size: $([long]($estimated * ((($sf - 1) / 2) + 1))) +/- $([long]($estimated * ($sf - 1) / 2)) MB" + Write-Host " The estimated process time using an SSD is about $([math]::ceiling($estimated/4000)) minutes." + } + if (($estimated * $sf) -lt ($freedisk / 1MB)) { + # There is enough free space in the TEMP drive or a data image. + if ($compact) { + # we are in compact mode, we process the image. + if ((!$data) -and ("$wsl_distro" -match "data")) { + Write-Host " Bypassing data image. use -d option to force processing of data images." + Continue + } + Write-Host " NOTE: You can safely cancel at any time by pressing Ctrl-C`n " -NoNewLine + remove-item "$tmp_folder/*" -Recurse -Force + wsl --shutdown + cmd /c "wsl --export ""$wsl_distro"" - | wsl --import wslclean ""$tmp_folder"" -" + wsl --shutdown + if (Test-Path "$tmp_folder/ext4.vhdx") { + Move-Item "$tmp_folder/ext4.vhdx" "$tmp_folder/$wsl_distro.vhdx" -Force + wsl --unregister wslclean | Out-Null + $size2 = (Get-Item -Path "$tmp_folder/$wsl_distro.vhdx").Length / 1MB + Write-Host " New Image compacted from $size1 MB to $size2 MB" + $answer = if ($force) { 'y' } else { read-host -prompt " Do you want to apply changes and use the new image (y/N)" } + if ($answer -match 'y') { + Move-Item "$tmp_folder/$wsl_distro.vhdx" "$wsl_path/ext4.vhdx" -Force + Write-Host " Image replaced for distro: $wsl_distro" + } + } + else { + Write-Host " WARNING: wslcompact found errors in the current image. It could be a storage problem," + Write-Host " a corrupted ext4 filesystem, or any other issue. Image not processed." + } + } + } + else { + # There isn't enough free space in the TEMP drive + write-Host " WARNING: there isn't enough free space in temp drive"(Get-PSDrive $env:TEMP[0])"to process $wsl_distro." + write-Host " There are only $([long]($freedisk / 1MB)) MB available." + write-Host "" + write-Host " Please, change the TEMP folder to a drive with at least $([long]($estimated * $sf / 1MB)) MB of free space." + write-Host " You cand do it by typing `$env:TEMP=`"Z:/your/new/temp/folder`" before using wslcompact.`n" + } + } + } + Remove-Item -Recurse -Force "$tmp_folder" + write-Host "" +} +Export-ModuleMember -Function 'wslcompact'