Wsl-RootFS.psm1
# Copyright 2022 Antoine Martin # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. using namespace System.IO; . "$PSScriptRoot\download.ps1" # The base URLs for Incus images $base_incus_url = "https://images.linuxcontainers.org/images" # We don't support ARM yet $incus_directory_suffix = "amd64/default" $incus_rootfs_name = "rootfs.tar.xz" $base_wsl_directory = "$env:LOCALAPPDATA\Wsl" $base_rootfs_directory = [DirectoryInfo]::new("$base_wsl_directory\RootFS") class UnknownIncusDistributionException : System.SystemException { UnknownIncusDistributionException([string] $Os, [string]$Release) : base("Unknown Incus distribution with OS $Os and Release $Release. Check $base_incus_url.") { } } <# .SYNOPSIS Returns the URL of the root filesystem of the Incus image for the specified OS and Release. .DESCRIPTION Incus images made by canonical (https://images.linuxcontainers.org/images) are "rolling". In Consequence, getting the current root filesystem URL for a distro Involves browsing the distro directory to get the directory name of the last build. .PARAMETER Os Parameter The name of the OS (debian, ubuntu, alpine, rockylinux, centos, ...) .PARAMETER Release The release (version). Highly dependent on the distro. For rolling release distros (e.g. Arch), use `current`. .OUTPUTS string The URL of the root filesystem for the requested distribution. .EXAMPLE Get-LxdRootFSUrl almalinux 8 Returns the URL of the root filesystem for almalinux version 8 .EXAMPLE Get-LxdRootFSUrl -Os centos -Release 9-Stream Returns the URL of the root filesystem for CentOS Stream version 9 #> function Get-LxdRootFSUrl { param ( [Parameter(Position = 0, Mandatory = $true)] [string]$Os, [Parameter(Position = 1, Mandatory = $true)] [string]$Release ) $url = "$base_incus_url/$Os/$Release/$incus_directory_suffix" try { $last_release_directory = (Invoke-WebRequest $url).Links | Select-Object -Last 1 -ExpandProperty "href" } catch { throw [UnknownIncusDistributionException]::new($OS, $Release) } return [System.Uri]"$url/$last_release_directory$incus_rootfs_name" } enum WslRootFileSystemState { NotDownloaded Synced Outdated } enum WslRootFileSystemType { Builtin Incus Local Uri } class UnknownDistributionException : System.SystemException { UnknownDistributionException([string] $Name) : base("Unknown distribution(s): $Name") { } } function Emoji { param ( [string]$code ) $EmojiIcon = [System.Convert]::toInt32($code, 16) return [System.Char]::ConvertFromUtf32($EmojiIcon) } $script:HourGlass = Emoji "231B" $script:PartyPopper = Emoji "1F389" $script:Eyes = Emoji "1F440" function Progress { param ( [string]$message ) Write-Host "$script:HourGlass " -NoNewline Write-Host -ForegroundColor DarkGray $message } function Success { param ( [string]$message ) Write-Host "$script:PartyPopper " -NoNewline Write-Host -ForegroundColor DarkGreen $message } function Information { param ( [string]$message ) Write-Host "$script:Eyes " -NoNewline Write-Host -ForegroundColor DarkYellow $message } # This function is here to mock the download in unit tests function Sync-File { param( [System.Uri]$Url, [FileInfo]$File ) Progress "Downloading $($Url)..." Start-Download $Url $File.FullName } # Another function to mock in unit tests function Sync-String { param( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] [System.Uri]$Url ) process { return (New-Object Net.WebClient).DownloadString($Url) } } class WslRootFileSystemHash { [System.Uri]$Url [string]$Algorithm [string]$Type [hashtable]$Hashes = @{} [bool]$Mandatory = $true [void]Retrieve() { Progress "Getting checksums from $($this.Url)..." try { $content = Sync-String $this.Url if ($this.Type -eq 'sums') { ForEach ($line in $($content -split "`n")) { if ([bool]$line) { $item = $line -split '\s+' $this.Hashes[$item[1]] = $item[0] } } } else { $filename = $this.Url.Segments[-1] -replace '\.\w+$', '' $this.Hashes[$filename] = $content.Trim() } } catch [System.Net.WebException] { if ($this.Mandatory) { throw $_ } } } [string]DownloadAndCheckFile([System.Uri]$Uri, [FileInfo]$Destination) { $Filename = $Uri.Segments[-1] if (!($this.Hashes.ContainsKey($Filename)) -and $this.Mandatory) { return $null } $expected = $this.Hashes[$Filename] $temp = [FileInfo]::new($Destination.FullName + '.tmp') try { Sync-File $Uri $temp $actual = (Get-FileHash -Path $temp.FullName -Algorithm $this.Algorithm).Hash if (($null -ne $expected) -and ($expected -ne $actual)) { Remove-Item -Path $temp.FullName -Force throw "Bad hash for $Uri -> $Destination : expected $expected, got $actual" } Move-Item $temp.FullName $Destination.FullName -Force return $actual } finally { Remove-Item $temp -Force -ErrorAction SilentlyContinue } } } class WslRootFileSystem: System.IComparable { [void] init([string]$Name, [bool]$Configured) { # Get the root fs file locally if ($Name -match '^incus:(?<Os>[^:]+):(?<Release>[^:]+)$') { $this.Type = [WslRootFileSystemType]::Incus $this.Os = $Matches.Os $this.Release = $Matches.Release $this.Url = Get-LxdRootFSUrl -Os:$this.Os -Release:$this.Release $this.AlreadyConfigured = $Configured $this.LocalFileName = "incus.$($this.Os)_$($this.Release).rootfs.tar.gz" $this.HashSource = [PSCustomObject]@{ Url = [System.Uri]::new($this.Url, "SHA256SUMS") Type = 'sums' Algorithm = 'SHA256' } } else { $this.Url = [System.Uri]$Name if ($this.Url.IsAbsoluteUri) { $this.LocalFileName = $this.Url.Segments[-1] $this.AlreadyConfigured = $Configured $this.Os = ($this.LocalFileName -split "[-. ]")[0] $this.Type = [WslRootFileSystemType]::Uri $this.HashSource = [PSCustomObject]@{ Url = [System.Uri]::new($this.Url, "SHA256SUMS") Type = 'sums' Algorithm = 'SHA256' Mandatory = $false } } else { $this.Url = $null $dist_lower = $Name.ToLower() $dist_title = (Get-Culture).TextInfo.ToTitleCase($dist_lower) $urlKey = 'Url' $hashKey = 'Hash' $rootfs_prefix = '' if ($true -eq $Configured) { $urlKey = 'ConfiguredUrl' $hashKey = 'ConfiguredHash' $rootfs_prefix = 'miniwsl.' } $this.LocalFileName = "$rootfs_prefix$dist_lower.rootfs.tar.gz" if ([WslRootFileSystem]::Distributions.ContainsKey($dist_title)) { $properties = [WslRootFileSystem]::Distributions[$dist_title] if (!$properties.ContainsKey($urlKey)) { throw "No configured Root filesystem for $dist_title." } $this.Os = $dist_title $this.Url = [System.Uri]$properties[$urlKey] $this.AlreadyConfigured = $Configured $this.Type = [WslRootFileSystemType]::Builtin $this.Release = $properties['Release'] $this.HashSource = $properties[$hashKey] } elseif ($this.IsAvailableLocally) { $this.Type = [WslRootFileSystemType]::Local $this.Os = $Name $this.AlreadyConfigured = $true # We assume it's already configured, but actually we don't know } else { # If the file is already present, take it throw [UnknownDistributionException] $Name } } } if ($this.IsAvailableLocally) { $this.State = [WslRootFileSystemState]::Synced $this.ReadMetaData() } else { $this.State = [WslRootFileSystemState]::NotDownloaded } } WslRootFileSystem([string]$Name, [bool]$Configured) { $this.init($Name, $Configured) } WslRootFileSystem([string]$Name) { $this.init($Name, $false) } WslRootFileSystem([FileInfo]$File) { $this.LocalFileName = $File.Name $this.State = [WslRootFileSystemState]::Synced if (!($this.ReadMetaData())) { $name = $File.Name -replace '\.rootfs\.tar\.gz$', '' if ($name.StartsWith("miniwsl.")) { $this.AlreadyConfigured = $true $this.Type = [WslRootFileSystemType]::Builtin $name = (Get-Culture).TextInfo.ToTitleCase(($name -replace 'miniwsl\.', '')) $this.Os = $name $this.Release = [WslRootFileSystem]::Distributions[$name]['Release'] $this.Url = [WslRootFileSystem]::Distributions[$name]['ConfiguredUrl'] } elseif ($name.StartsWith("incus.")) { $this.AlreadyConfigured = $false $this.Type = [WslRootFileSystemType]::Incus $this.Os, $this.Release = ($name -replace 'incus\.', '') -Split '_' $this.Url = Get-LxdRootFSUrl -Os $this.Os -Release $this.Release } else { $name = (Get-Culture).TextInfo.ToTitleCase($name) $this.Os = $name if ([WslRootFileSystem]::Distributions.ContainsKey($name)) { $this.AlreadyConfigured = $false $this.Type = [WslRootFileSystemType]::Builtin $this.Release = [WslRootFileSystem]::Distributions[$name]['Release'] $this.Url = [WslRootFileSystem]::Distributions[$name]['Url'] } else { $this.Type = [WslRootFileSystemType]::Local $this.Os = $name $this.Release = "unknown" $this.AlreadyConfigured = $true } } $this.WriteMetadata() } } [string] ToString() { return $this.OsName } [int] CompareTo([object] $obj) { $other = [WslRootFileSystem]$obj return $this.LocalFileName.CompareTo($other.LocalFileName) } [void]WriteMetadata() { [PSCustomObject]@{ Os = $this.Os Release = $this.Release Type = $this.Type.ToString() State = $this.State.ToString() Url = $this.Url AlreadyConfigured = $this.AlreadyConfigured HashSource = $this.HashSource FileHash = $this.FileHash # TODO: Checksums } | ConvertTo-Json | Set-Content -Path "$($this.File.FullName).json" } [bool]ReadMetaData() { $metadata_filename = "$($this.File.FullName).json" $result = $false $rewrite_it = $false if (Test-Path $metadata_filename) { $metadata = Get-Content $metadata_filename | ConvertFrom-Json $this.Os = $metadata.Os $this.Release = $metadata.Release $this.Type = [WslRootFileSystemType]($metadata.Type) $this.State = [WslRootFileSystemState]($metadata.State) if (!$this.Url) { $this.Url = $metadata.Url } $this.AlreadyConfigured = $metadata.AlreadyConfigured if ($metadata.HashSource -and !$this.HashSource) { $this.HashSource = $metadata.HashSource } if ($metadata.FileHash) { $this.FileHash = $metadata.FileHash } $result = $true } if (!$this.FileHash) { if (!$this.HashSource) { $this.HashSource = [PSCustomObject]@{ Algorithm = 'SHA256' } } $this.FileHash = (Get-FileHash -Path $this.File.FullName -Algorithm $this.HashSource.Algorithm).Hash $rewrite_it = $true } if ($rewrite_it) { $this.WriteMetadata() } return $result } [bool]Delete() { if ($this.IsAvailableLocally) { Remove-Item -Path $this.File.FullName Remove-Item -Path "$($this.File.FullName).json" -ErrorAction SilentlyContinue $this.State = [WslRootFileSystemState]::NotDownloaded return $true } return $false } static [WslRootFileSystem[]] AllFileSystems() { $path = [WslRootFileSystem]::BasePath $files = $path.GetFiles("*.tar.gz") $local = [WslRootFileSystem[]]( $files | ForEach-Object { [WslRootFileSystem]::new($_) }) $builtin = [WslRootFileSystem]::Distributions.keys | ForEach-Object { [WslRootFileSystem]::new($_, $false) [WslRootFileSystem]::new($_, $true) } return ($local + $builtin) | Sort-Object | Get-Unique } [WslRootFileSystemHash]GetHashSource() { if ($this.HashSource) { $hashUrl = $this.HashSource.Url if ([WslRootFileSystem]::HashSources.ContainsKey($hashUrl)) { return [WslRootFileSystem]::HashSources[$hashUrl] } else { $source = [WslRootFileSystemHash]($this.HashSource) $source.Retrieve() [WslRootFileSystem]::HashSources[$hashUrl] = $source return $source } } return $null } [System.Uri]$Url [WslRootFileSystemState]$State [WslRootFileSystemType]$Type [bool]$AlreadyConfigured [string]$Os [string]$Release = "unknown" [string]$LocalFileName [PSCustomObject]$HashSource [string]$FileHash static [DirectoryInfo]$BasePath = $base_rootfs_directory # This is indexed by the URL static [hashtable]$HashSources = @{} static $BuiltinHashes = [PSCustomObject]@{ Url = 'https://github.com/antoinemartin/PowerShell-Wsl-Manager/releases/latest/download/SHA256SUMS' Algorithm = 'SHA256' Type = 'sums' } static $Distributions = @{ Arch = @{ Url = 'https://github.com/antoinemartin/PowerShell-Wsl-Manager/releases/latest/download/archlinux.rootfs.tar.gz' Hash = [WslRootFileSystem]::BuiltinHashes ConfiguredUrl = 'https://github.com/antoinemartin/PowerShell-Wsl-Manager/releases/latest/download/miniwsl.arch.rootfs.tar.gz' ConfiguredHash = [WslRootFileSystem]::BuiltinHashes Release = 'current' } Alpine = @{ Url = 'https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-minirootfs-3.19.1-x86_64.tar.gz' Hash = [PSCustomObject]@{ Url = 'https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-minirootfs-3.19.1-x86_64.tar.gz.sha256' Algorithm = 'SHA256' Type = 'sums' } ConfiguredUrl = 'https://github.com/antoinemartin/PowerShell-Wsl-Manager/releases/latest/download/miniwsl.alpine.rootfs.tar.gz' ConfiguredHash = [WslRootFileSystem]::BuiltinHashes Release = '3.19' } Ubuntu = @{ Url = 'https://cloud-images.ubuntu.com/wsl/noble/current/ubuntu-noble-wsl-amd64-wsl.rootfs.tar.gz' Hash = [PSCustomObject]@{ Url = 'https://cloud-images.ubuntu.com/wsl/noble/current/SHA256SUMS' Algorithm = 'SHA256' Type = 'sums' } ConfiguredUrl = 'https://github.com/antoinemartin/PowerShell-Wsl-Manager/releases/latest/download/miniwsl.ubuntu.rootfs.tar.gz' ConfiguredHash = [WslRootFileSystem]::BuiltinHashes Release = 'noble' } Debian = @{ # This is the root fs used to produce the official Debian slim docker image # see https://github.com/docker-library/official-images/blob/master/library/debian # see https://github.com/debuerreotype/docker-debian-artifacts Url = "https://doi-janky.infosiftr.net/job/tianon/job/debuerreotype/job/amd64/lastSuccessfulBuild/artifact/stable/rootfs.tar.xz" Hash = [PSCustomObject]@{ Url = 'https://doi-janky.infosiftr.net/job/tianon/job/debuerreotype/job/amd64/lastSuccessfulBuild/artifact/stable/rootfs.tar.xz.sha256' Algorithm = 'SHA256' Type = 'single' } ConfiguredUrl = "https://github.com/antoinemartin/PowerShell-Wsl-Manager/releases/latest/download/miniwsl.debian.rootfs.tar.gz" ConfiguredHash = [WslRootFileSystem]::BuiltinHashes Release = 'bookworm' } OpenSuse = @{ Url = "https://download.opensuse.org/tumbleweed/appliances/opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz" Hash = [PSCustomObject]@{ Url = 'https://download.opensuse.org/tumbleweed/appliances/opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz.sha256' Algorithm = 'SHA256' Type = 'sums' } ConfiguredUrl = "https://github.com/antoinemartin/PowerShell-Wsl-Manager/releases/latest/download/miniwsl.opensuse.rootfs.tar.gz" ConfiguredHash = [WslRootFileSystem]::BuiltinHashes Release = 'tumbleweed' } } } <# .SYNOPSIS Creates a new FileSystem hash holder. .DESCRIPTION The WslRootFileSystemHash object holds checksum information for one or more distributions in order to check it upon download and determine if the filesystem has been updated. Note that the checksums are not downloaded until the `Retrieve()` method has been called on the object. .PARAMETER Url The Url where the checksums are located. .PARAMETER Algorithm The checksum algorithm. Nowadays, we find mostly SHA256. .PARAMETER Type Type can either be `sums` in which case the file contains one <checksum> <filename> pair per line, or `single` and just contains the hash for the file which name is the last segment of the Url minus the extension. For instance, if the URL is `https://.../rootfs.tar.xz.sha256`, we assume that the checksum it contains is for the file named `rootfs.tar.xz`. .EXAMPLE New-WslRootFileSystemHash https://cloud-images.ubuntu.com/wsl/noble/current/SHA256SUMS Creates the hash source for several files with SHA256 (default) algorithm. .EXAMPLE New-WslRootFileSystemHash https://.../rootfs.tar.xz.sha256 -Type `single` Creates the hash source for the rootfs.tar.xz file with SHA256 (default) algorithm. .NOTES General notes #> function New-WslRootFileSystemHash { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$Url, [Parameter(Mandatory = $false)] [string]$Algorithm = 'SHA256', [Parameter(Mandatory = $false)] [string]$Type = 'sums' ) return [WslRootFileSystemHash]@{ Url = $Url Algorithm = $Algorithm Type = $Type } } function New-WslRootFileSystem { <# .SYNOPSIS Creates a WslRootFileSystem object. .DESCRIPTION WslRootFileSystem object retrieve and provide information about available root filesystems. .PARAMETER Distribution The identifier of the distribution. It can be an already known name: - Arch - Alpine - Ubuntu - Debian It also can be the URL (https://...) of an existing filesystem or a distribution name saved through Export-Wsl. It can also be a name in the form: incus:<os>:<release> (ex: incus:rockylinux:9) In this case, it will fetch the last version the specified image in https://images.linuxcontainers.org/images. .PARAMETER Configured Whether the distribution is configured. This parameter is relevant for Builtin distributions. .PARAMETER Path The path of the root filesystem. Should be a file ending with `rootfs.tar.gz`. .PARAMETER File A FileInfo object of the compressed root filesystem. .EXAMPLE New-WslRootFileSystem incus:alpine:3.19 Type Os Release State Name ---- -- ------- ----- ---- Incus alpine 3.19 Synced incus.alpine_3.19.rootfs.tar.gz The WSL root filesystem representing the incus alpine 3.19 image. .EXAMPLE New-WslRootFileSystem alpine -Configured Type Os Release State Name ---- -- ------- ----- ---- Builtin Alpine 3.19 Synced miniwsl.alpine.rootfs.tar.gz The builtin configured Alpine root filesystem. .LINK Get-WslRootFileSystem #> [CmdletBinding()] param ( [Parameter(Position = 0, ParameterSetName = 'Name', Mandatory = $true)] [string]$Distribution, [Parameter(Position = 1, ParameterSetName = 'Name', Mandatory = $false)] [switch]$Configured, [Parameter(ParameterSetName = 'Path', ValueFromPipeline = $true, Mandatory = $true)] [string]$Path, [Parameter(ParameterSetName = 'File', ValueFromPipeline = $true, Mandatory = $true)] [FileInfo]$File ) process { if ($PSCmdlet.ParameterSetName -eq "Name") { return [WslRootFileSystem]::new($Distribution, $Configured) } else { if ($PSCmdlet.ParameterSetName -eq "Path") { $File = [FileInfo]::new($Path) } return [WslRootFileSystem]::new($File) } } } function Sync-WslRootFileSystem { <# .SYNOPSIS Synchronize locally the specified WSL root filesystem. .DESCRIPTION If the root filesystem is not already present locally, downloads it from its original URL. .PARAMETER Distribution The identifier of the distribution. It can be an already known name: - Arch - Alpine - Ubuntu - Debian It also can be the URL (https://...) of an existing filesystem or a distribution name saved through Export-Wsl. It can also be a name in the form: incus:<os>:<release> (ex: incus:rockylinux:9) In this case, it will fetch the last version the specified image in https://images.linuxcontainers.org/images. .PARAMETER Configured Whether the distribution is configured. This parameter is relevant for Builtin distributions. .PARAMETER RootFileSystem The WslRootFileSystem object to process. .PARAMETER Force Force the synchronization even if the root filesystem is already present locally. .INPUTS The WSLRootFileSystem Objects to process. .OUTPUTS The path of the WSL root filesytem. It is suitable as input for the `wsl --import` command. .EXAMPLE Sync-WslRootFileSystem Alpine -Configured Syncs the already configured builtin Alpine root filesystem. .EXAMPLE Sync-WslRootFileSystem Alpine -Force Re-download the Alpine builtin root filesystem. .EXAMPLE Get-WslRootFileSystem -State NotDownloaded -Os Alpine | Sync-WslRootFileSystem Synchronize the Alpine root filesystems not already synced .EXAMPLE New-WslRootFileSystem alpine -Configured | Sync-WslRootFileSystem | % { &wsl --import test $env:LOCALAPPDATA\Wsl\test $_ } Create a WSL distro from a synchronized root filesystem. .LINK New-WslRootFileSystem Get-WslRootFileSystem #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Position = 0, ParameterSetName = 'Name', Mandatory = $true)] [string]$Distribution, [Parameter(ParameterSetName = 'Name', Mandatory = $false)] [switch]$Configured, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "RootFileSystem")] [WslRootFileSystem[]]$RootFileSystem, [Parameter(Mandatory = $false)] [switch]$Force ) process { if ($PSCmdlet.ParameterSetName -eq "Name") { $RootFileSystem = New-WslRootFileSystem $Distribution -Configured:$Configured } if ($null -ne $RootFileSystem) { $RootFileSystem | ForEach-Object { $fs = $_ [FileInfo] $dest = $fs.File If (!([WslRootFileSystem]::BasePath.Exists)) { if ($PSCmdlet.ShouldProcess([WslRootFileSystem]::BasePath.Create(), "Create base path")) { Progress "Creating rootfs base path [$([WslRootFileSystem]::BasePath)]..." [WslRootFileSystem]::BasePath.Create() } } if (!$dest.Exists -Or $_.Outdated -Or $true -eq $Force) { if ($PSCmdlet.ShouldProcess($fs.Url, "Sync locally")) { try { $fs.FileHash = $fs.GetHashSource().DownloadAndCheckFile($fs.Url, $fs.File) } catch [Exception] { throw "Error while loading distro [$($fs.OsName)] on $($fs.Url): $($_.Exception.Message)" return $null } $fs.State = [WslRootFileSystemState]::Synced $fs.WriteMetadata() Success "[$($fs.OsName)] Synced at [$($dest.FullName)]." } } else { Information "[$($fs.OsName)] Root FS already at [$($dest.FullName)]." } return $dest.FullName } } } } function Get-WslRootFileSystem { <# .SYNOPSIS Gets the WSL root filesystems installed on the computer and the ones available. .DESCRIPTION The Get-WslRootFileSystem cmdlet gets objects that represent the WSL root filesystems available on the computer. This can be the ones already synchronized as well as the Bultin filesystems available. .PARAMETER Name Specifies the name of the filesystem. .PARAMETER Os Specifies the Os of the filesystem. .PARAMETER Type Specifies the type of the filesystem. .PARAMETER Outdated Return the list of outdated root filesystems. Works mainly on Builtin distributions. .INPUTS System.String You can pipe a distribution name to this cmdlet. .OUTPUTS WslRootFileSystem The cmdlet returns objects that represent the WSL root filesystems on the computer. .EXAMPLE Get-WslRootFileSystem Type Os Release State Name ---- -- ------- ----- ---- Builtin Alpine 3.19 NotDownloaded alpine.rootfs.tar.gz Builtin Arch current Synced arch.rootfs.tar.gz Builtin Debian bookworm Synced debian.rootfs.tar.gz Local Docker unknown Synced docker.rootfs.tar.gz Local Flatcar unknown Synced flatcar.rootfs.tar.gz Incus almalinux 8 Synced incus.almalinux_8.rootfs.tar.gz Incus almalinux 9 Synced incus.almalinux_9.rootfs.tar.gz Incus alpine 3.19 Synced incus.alpine_3.19.rootfs.tar.gz Incus alpine edge Synced incus.alpine_edge.rootfs.tar.gz Incus centos 9-Stream Synced incus.centos_9-Stream.rootfs.ta... Incus opensuse 15.4 Synced incus.opensuse_15.4.rootfs.tar.gz Incus rockylinux 9 Synced incus.rockylinux_9.rootfs.tar.gz Builtin Alpine 3.19 Synced miniwsl.alpine.rootfs.tar.gz Builtin Arch current Synced miniwsl.arch.rootfs.tar.gz Builtin Debian bookworm Synced miniwsl.debian.rootfs.tar.gz Builtin Opensuse tumbleweed Synced miniwsl.opensuse.rootfs.tar.gz Builtin Ubuntu noble NotDownloaded miniwsl.ubuntu.rootfs.tar.gz Local Netsdk unknown Synced netsdk.rootfs.tar.gz Builtin Opensuse tumbleweed Synced opensuse.rootfs.tar.gz Local Out unknown Synced out.rootfs.tar.gz Local Postgres unknown Synced postgres.rootfs.tar.gz Builtin Ubuntu noble Synced ubuntu.rootfs.tar.gz Get all WSL root filesystem. .EXAMPLE Get-WslRootFileSystem -Os alpine Type Os Release State Name ---- -- ------- ----- ---- Builtin Alpine 3.19 NotDownloaded alpine.rootfs.tar.gz Incus alpine 3.19 Synced incus.alpine_3.19.rootfs.tar.gz Incus alpine edge Synced incus.alpine_edge.rootfs.tar.gz Builtin Alpine 3.19 Synced miniwsl.alpine.rootfs.tar.gz Get All Alpine root filesystems. .EXAMPLE Get-WslRootFileSystem -Type Incus Type Os Release State Name ---- -- ------- ----- ---- Incus almalinux 8 Synced incus.almalinux_8.rootfs.tar.gz Incus almalinux 9 Synced incus.almalinux_9.rootfs.tar.gz Incus alpine 3.19 Synced incus.alpine_3.19.rootfs.tar.gz Incus alpine edge Synced incus.alpine_edge.rootfs.tar.gz Incus centos 9-Stream Synced incus.centos_9-Stream.rootfs.ta... Incus opensuse 15.4 Synced incus.opensuse_15.4.rootfs.tar.gz Incus rockylinux 9 Synced incus.rockylinux_9.rootfs.tar.gz Get All downloaded Incus root filesystems. #> [CmdletBinding()] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [SupportsWildcards()] [string[]]$Name, [Parameter(Mandatory = $false)] [string]$Os, [Parameter(Mandatory = $false)] [WslRootFileSystemState]$State, [Parameter(Mandatory = $false)] [WslRootFileSystemType]$Type, [Parameter(Mandatory = $false)] [switch]$Configured, [Parameter(Mandatory = $false)] [switch]$Outdated ) process { $fses = [WslRootFileSystem]::AllFileSystems() if ($PSBoundParameters.ContainsKey("Type")) { $fses = $fses | Where-Object { $_.Type -eq $Type } } if ($PSBoundParameters.ContainsKey("Os")) { $fses = $fses | Where-Object { $_.Os -eq $Os } } if ($PSBoundParameters.ContainsKey("State")) { $fses = $fses | Where-Object { $_.State -eq $State } } if ($PSBoundParameters.ContainsKey("Configured")) { $fses = $fses | Where-Object { $_.AlreadyConfigured -eq $Configured.IsPresent } } if ($PSBoundParameters.ContainsKey("Outdated")) { $fses = $fses | Where-Object { $_.Outdated } } if ($Name.Length -gt 0) { $fses = $fses | Where-Object { foreach ($pattern in $Name) { if ($_.Name -ilike $pattern) { return $true } } return $false } if ($null -eq $fses) { throw [UnknownDistributionException]::new($Name) } } return $fses } } <# .SYNOPSIS Remove a WSL root filesystem from the local disk. .DESCRIPTION If the WSL root filesystem in synced, it will remove the tar file and its meta data from the disk. Builtin root filesystems will still appear as output of `Get-WslRootFileSystem`, but their state will be `NotDownloaded`. .PARAMETER Distribution The identifier of the distribution. It can be an already known name: - Arch - Alpine - Ubuntu - Debian It also can be the URL (https://...) of an existing filesystem or a distribution name saved through Export-Wsl. It can also be a name in the form: incus:<os>:<release> (ex: incus:rockylinux:9) In this case, it will fetch the last version the specified image in https://images.linuxcontainers.org/images. .PARAMETER Configured Whether the root filesystem is already configured. This parameter is relevant only for Builtin distributions. .PARAMETER RootFileSystem The WslRootFileSystem object representing the WSL root filesystem to delete. .INPUTS One or more WslRootFileSystem objects representing the WSL root filesystem to delete. .OUTPUTS The WSLRootFileSytem objects updated. .EXAMPLE Remove-WslRootFileSystem alpine -Configured Removes the builtin configured alpine root filesystem. .EXAMPLE New-WslRootFileSystem "incus:alpine:3.19" | Remove-WslRootFileSystem Removes the Incus alpine 3.19 root filesystem. .EXAMPLE Get-WslRootFilesystem -Type Incus | Remove-WslRootFileSystem Removes all the Incus root filesystems present locally. .Link Get-WslRootFileSystem New-WslRootFileSystem #> Function Remove-WslRootFileSystem { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Position = 0, ParameterSetName = 'Name', Mandatory = $true)] [string]$Distribution, [Parameter(ParameterSetName = 'Name', Mandatory = $false)] [switch]$Configured, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "RootFileSystem")] [WslRootFileSystem[]]$RootFileSystem ) process { if ($PSCmdlet.ParameterSetName -eq "Name") { $RootFileSystem = New-WslRootFileSystem $Distribution -Configured:$Configured } if ($null -ne $RootFileSystem) { $RootFileSystem | ForEach-Object { if ($_.Delete()) { $_ } } } } } <# .SYNOPSIS Get the list of available Incus based root filesystems. .DESCRIPTION This command retrieves the list of available Incus root filesystems from the Canonical site: https://images.linuxcontainers.org/imagesstreams/v1/index.json .PARAMETER Name List of names or wildcard based patterns to select the Os. .EXAMPLE Get-IncusRootFileSystem Retrieve the complete list of Incus root filesystems .EXAMPLE Get-IncusRootFileSystem alma* Os Release -- ------- almalinux 8 almalinux 9 Get all alma based filesystems. .EXAMPLE Get-IncusRootFileSystem mint | %{ New-WslRootFileSystem "incus:$($_.Os):$($_.Release)" } Type Os Release State Name ---- -- ------- ----- ---- Incus mint tara NotDownloaded incus.mint_tara.rootfs.tar.gz Incus mint tessa NotDownloaded incus.mint_tessa.rootfs.tar.gz Incus mint tina NotDownloaded incus.mint_tina.rootfs.tar.gz Incus mint tricia NotDownloaded incus.mint_tricia.rootfs.tar.gz Incus mint ulyana NotDownloaded incus.mint_ulyana.rootfs.tar.gz Incus mint ulyssa NotDownloaded incus.mint_ulyssa.rootfs.tar.gz Incus mint uma NotDownloaded incus.mint_uma.rootfs.tar.gz Incus mint una NotDownloaded incus.mint_una.rootfs.tar.gz Incus mint vanessa NotDownloaded incus.mint_vanessa.rootfs.tar.gz Get all mint based Incus root filesystems as WslRootFileSystem objects. #> function Get-IncusRootFileSystem { [CmdletBinding()] param ( [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [SupportsWildcards()] [string[]]$Name ) process { $fses = Sync-String "https://images.linuxcontainers.org/imagesstreams/v1/index.json" | ConvertFrom-Json | ForEach-Object { $_.index.images.products } | Select-String 'amd64:default$' | ForEach-Object { $_ -replace '^(?<distro>[^:]+):(?<release>[^:]+):.*', '${distro},"${release}"' } | ConvertFrom-Csv -Header Os, Release if ($Name.Length -gt 0) { $fses = $fses | Where-Object { foreach ($pattern in $Name) { if ($_.Os -ilike $pattern) { return $true } } return $false } if ($null -eq $fses) { throw [UnknownDistributionException]::new($Name) } } return $fses } } Export-ModuleMember New-WslRootFileSystem Export-ModuleMember Sync-File Export-ModuleMember Sync-WslRootFileSystem Export-ModuleMember Get-WslRootFileSystem Export-ModuleMember Remove-WslRootFileSystem Export-ModuleMember Get-IncusRootFileSystem Export-ModuleMember New-WslRootFileSystemHash Export-ModuleMember Progress Export-ModuleMember Success Export-ModuleMember Information |