|
function ConvertFrom-IniContent { |
|
<# |
|
.SYNOPSIS |
|
Parses content from ini/conf files into nested hashtables. |
|
.EXAMPLE |
|
Get-Content \\wsl$\Ubuntu\etc\wsl.conf | ConvertFrom-IniContent |
|
.EXAMPLE |
|
ConvertFrom-IniContent (Get-Content ~\.wslconf -Raw) |
|
.EXAMPLE |
|
" |
|
[automount] |
|
enabled = true |
|
mountFsTab = true |
|
|
|
[network] |
|
generateResolvConf = false |
|
" | ConvertFrom-IniContent |
|
|
|
Name Value |
|
---- ----- |
|
automount {enabled, mountFsTab} |
|
network {generateResolvConf} |
|
|
|
#> |
|
[CmdletBinding()] |
|
param( |
|
# The content of an ini file |
|
[Parameter(Mandatory, ValueFromPipeline)] |
|
[AllowEmptyString()] |
|
[string]$InputObject |
|
) |
|
begin { |
|
$StringBuilder = [System.Collections.Generic.List[string]]::new() |
|
} |
|
process { |
|
$StringBuilder.AddRange([string[]]@($InputObject -split "[\r\n]+")) |
|
} |
|
end { |
|
$ini = [ordered]@{} |
|
switch -regex ($StringBuilder) { |
|
"^\s*\[(.*)\]\s*$" { |
|
$section = $ini |
|
foreach ($level in $matches[1] -split "\.") { |
|
if (!$section[$level]) { |
|
$section[$level] = [ordered]@{} |
|
} |
|
$section = $section[$level] |
|
} |
|
} |
|
"^\s*(.+?)\s*=\s*(.+?)\s*$" { |
|
$name, $value = $matches[1..2] |
|
$section[$name] = $value |
|
} |
|
} |
|
$ini |
|
} |
|
} |
|
|
|
function ConvertTo-IniContent { |
|
<# |
|
.SYNOPSIS |
|
Convert nested hashtables to ini syntax. |
|
Supports recursively nested hashtables by putting dots in the section names. |
|
.EXAMPLE |
|
Get-Content \\wsl$\Ubuntu\etc\wsl.conf | ConvertFrom-IniContent |
|
.EXAMPLE |
|
ConvertFrom-IniContent (Get-Content ~\.wslconf -Raw) |
|
.EXAMPLE |
|
@{ |
|
automount = @{ |
|
enabled = $true |
|
mountFsTab = $true |
|
} |
|
network = @{ |
|
generateResolvConf = $false |
|
} |
|
} | ConvertTo-IniContent |
|
|
|
[automount] |
|
enabled = True |
|
mountFsTab = True |
|
[network] |
|
generateResolvConf = False |
|
#> |
|
[CmdletBinding()] |
|
[OutputType([string])] |
|
param( |
|
[Parameter(Mandatory, ValueFromPipeline, Position = 0)] |
|
[System.Collections.IDictionary]$InputObject, |
|
|
|
[Parameter()] |
|
[string[]]$Section |
|
) |
|
process { |
|
if ($Section) { "[$($Section -join ".")]" } |
|
$after = @() |
|
foreach ($kv in $InputObject.GetEnumerator()) { |
|
if ($kv.value -is [System.Collections.IDictionary]) { |
|
$after += $kv |
|
} else { |
|
$kv.key + " = " + $kv.value |
|
} |
|
} |
|
foreach ($kv in $after) { |
|
$Nested = $section + $kv.Key |
|
$kv.value | ConvertTo-IniContent -Section $Nested |
|
} |
|
} |
|
} |
|
|
|
function Set-WslContent { |
|
# .SYNOPSIS |
|
# A wrapper for piping content into WSL files that aren't writeable as the default user. |
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] |
|
[CmdletBinding()] |
|
param( |
|
# The name of the linux distro |
|
[Parameter()] |
|
[string]$Distribution, |
|
|
|
# The linux file path |
|
[Parameter(Mandatory)] |
|
[string]$Path, |
|
|
|
[Parameter(Mandatory)] |
|
[ValidatePattern('^[^"]*$')] |
|
[Alias("Content")] |
|
[string[]]$InputObject |
|
) |
|
$ENV:WSLPASSCONTENT = $InputObject -join "`n" |
|
$env:WSLENV += ":WSLPASSCONTENT" |
|
if ($Distribution) { |
|
wsl --distribution $Distribution -u root sh -c "cat << EOF > $Path`n`${WSLPASSCONTENT}`nEOF" |
|
} else { |
|
wsl -u root sh -c "cat << EOF > $Path`n`${WSLPASSCONTENT}`nEOF" |
|
} |
|
$env:WSLENV = $env:WSLENV -replace ":WSLPASSCONTENT$" |
|
} |
|
|
|
function Update-WslCertificates { |
|
<# |
|
.SYNOPSIS |
|
Copy certificates from LocalMachine\Root to a WSL distro. |
|
Thanks ZScaler Internet Security. 😑 |
|
#> |
|
[CmdletBinding()] |
|
param( |
|
# Certificate thumbprints to copy into WSL distros for trust (for example, this one from ZScaler) |
|
[Parameter(ValueFromPipeline)] |
|
[array]$Certificates = "d72f47d87420e3f0f9bdcac6f03a566743c481b9", |
|
|
|
# The Distribution to configure (by default, all of them) |
|
# Be careful when calculating the values for this: |
|
# There is current a bug in wsl that causes it to output nulls after every character |
|
[string[]]$Distribution = $(wsl --list --quiet) |
|
) |
|
begin { |
|
$AllCertificates = [System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509Certificate2]]::new() |
|
} |
|
process { |
|
[System.Security.Cryptography.X509Certificates.X509Certificate2[]]$MoreCertificates = |
|
@($Certificates).Where{ $_ -is [System.Security.Cryptography.X509Certificates.X509Certificate2] } |
|
$null = $AllCertificates.AddRange($MoreCertificates) |
|
if (($CertificateNames = @($Certificates).Where{ $_ -is [string] })) { |
|
Push-Location Cert:\LocalMachine\Root |
|
$MoreCertificates = @(Get-Item $CertificateNames -ErrorAction Ignore).Where{ $_ -is [System.Security.Cryptography.X509Certificates.X509Certificate2] } |
|
$null = $AllCertificates.AddRange($MoreCertificates) |
|
Pop-Location |
|
} |
|
} |
|
end { |
|
foreach ($distro in $Distribution) { |
|
if (!(wsl --distribution $distro which update-ca-certificates)) { |
|
Write-Warning "$distro missing update-ca-certificates -- not updating Trusted.pem" |
|
continue |
|
} |
|
$AllCertificates | ForEach-Object { |
|
if (!($Name = $_.Subject -replace ".*CN=([^,]*).*", '$1' -replace " ", "_")) { |
|
$Name = $_.Thumbprint |
|
} |
|
Write-Information "Adding Certificate $($_.Thumbprint) for $($Name)" -InformationAction Continue |
|
$rawcert = @( |
|
"-----BEGIN CERTIFICATE-----" |
|
[Convert]::ToBase64String($_.RawData) |
|
"-----END CERTIFICATE-----" |
|
) |
|
Set-WslContent -Distribution $distro -Path "/usr/local/share/ca-certificates/$Name.crt" -Content $rawcert |
|
} |
|
wsl --distribution $distro -u root update-ca-certificates |
|
} |
|
} |
|
} |
|
|
|
function Update-WslDns { |
|
<# |
|
.SYNOPSIS |
|
Update the DNS servers on WSL distros to fix connectivity when VPNs mess with it. |
|
https://docs.microsoft.com/en-us/windows/wsl/troubleshooting#bash-loses-network-connectivity-once-connected-to-a-vpn |
|
|
|
.DESCRIPTION |
|
Since this reaches into each distro and runs commands with `sudo` one at a time, |
|
You MIGHT want to use `sudo visudo` on each distro to remove the password request from sudo to make this go smoothly. |
|
%sudo ALL=(ALL:ALL) NOPASSWD: ALL |
|
|
|
You can put it back at the end by removing "NOPASSWD:" from again. |
|
|
|
Recommended you run with -Verbose the first time |
|
#> |
|
[Alias("Update-WslResolv")] |
|
[CmdletBinding()] |
|
param( |
|
# DNS Servers you want to use in WSL (by default copied from your local DNS client settings) |
|
[ValidateNotNullOrEmpty()] |
|
[string[]]$DnsServers = $(Get-DnsClientServerAddress -AddressFamily IPv4 | Select-Object -Expand ServerAddresses -Unique), |
|
|
|
# DNS Suffixes you want to search in WSL (by default copied from your local DNS client settings) |
|
[ValidateNotNullOrEmpty()] |
|
[string[]]$DnsSuffixes = $((Get-DnsClientGlobalSetting).SuffixSearchList), |
|
|
|
# The Distribution to configure (by default, all of them) |
|
# Be careful when calculating the values for this: |
|
# There is current a bug in wsl that causes it to output nulls after every character |
|
[ValidateNotNullOrEmpty()] |
|
[string[]]$Distribution = $(wsle --list --quiet) |
|
) |
|
foreach ($distro in $Distribution) { |
|
Write-Verbose "Update /etc/resolv.conf for $distro" |
|
wsl --distribution $distro -u root sh -c "unlink /etc/resolv.conf" |
|
Set-WslContent -Distribution $Distro -Path /etc/resolv.conf @( |
|
if ($DnsSuffixes) { "search $($DnsSuffixes -join ' ')" } |
|
$DnsServers | ForEach-Object { "nameserver $_" } |
|
"nameserver 8.8.8.8" |
|
) |
|
} |
|
} |
|
|
|
function Disable-WslGenerateResolvConf { |
|
<# |
|
.SYNOPSIS |
|
Update wsl.conf to disable generateResolvConf |
|
#> |
|
[CmdletBinding()] |
|
param( |
|
# The Distribution to configure (by default, all of them) |
|
# Be careful when calculating the values for this: |
|
# There is current a bug in wsl that causes it to output nulls after every character |
|
[ValidateNotNullOrEmpty()] |
|
[string[]]$Distribution = $(wsle --list --quiet), |
|
|
|
# Force a restart of the distro(s) |
|
[switch]$Restart |
|
) |
|
foreach ($distro in $Distribution) { |
|
Write-Verbose "Update /etc/wsl.conf for $distro" |
|
#$wslConfPath = "\\wsl$\$distro\etc\wsl.conf" |
|
$wslConfPath = "/etc/wsl.conf" |
|
|
|
# Get the current content, if there is any |
|
if (($wsl = wsl --distribution $distro sh -c "cat $wslConfPath" | ConvertFrom-IniContent)) { |
|
if (!($wsl["network"])) { |
|
$wsl["network"] = [ordered]@{} |
|
} |
|
$wsl["network"]["generateResolvConf"] = "false" |
|
} else { |
|
$wsl = [ordered]@{ |
|
network = [ordered]@{ |
|
"generateResolvConf" = "false" |
|
} |
|
} |
|
} |
|
|
|
Set-WslContent -Distribution $Distro -Path $wslConfPath -Content ($wsl | ConvertTo-IniContent) |
|
|
|
if ($Restart) { |
|
# See The 8 Second Rule: https://docs.microsoft.com/en-us/windows/wsl/wsl-config#the-8-second-rule |
|
wsl --terminate $distro |
|
|
|
# There's a bug in wsl, it's outputting nulls after every character |
|
while ((wsle --list --running --quiet) -eq $distro) { |
|
Start-Sleep -Milliseconds 50 |
|
} |
|
|
|
# start it back up, hopefully it'll loose the /etc/resolv.conf |
|
wsl --distribution $distro echo hello |
|
} |
|
} |
|
} |
|
|
|
function Invoke-Wsl { |
|
<# |
|
.SYNOPSIS |
|
Wrap wsl.exe with Console.Encoding because it ignores Console.Encoding |
|
This way when we get UTF-16 encoding the console handles it. |
|
#> |
|
[Console]::OutputEncoding, $Encoding = [Text.Encoding]::Unicode, [Console]::OutputEncoding |
|
wsl.exe @args |
|
[Console]::OutputEncoding = $Encoding |
|
} |
|
|
|
Set-Alias wsle Invoke-Wsl |
|
|
|
$Commands = Get-Command *-Wsl* -Module WslHelper -ParameterName Distribution |
|
|
|
Register-ArgumentCompleter -CommandName $Commands.Name -ParameterName Distribution -ScriptBlock { |
|
param($CommandName, $ParameterName, $WordToComplete, $CommandLineAst, $PartialBoundParameters) |
|
wsle --list --quiet | ForEach-Object { |
|
[Management.Automation.CompletionResult]::new("'$($_)'", $_, "ParameterValue", $_) |
|
} |
|
} |