Two Steps and One Script to Run Linux on Windows Via WSL

By Zachary Loeber

Mar 212018

Windows 10 has been releasing progressively better upgrades to the Windows Subsystem for Linux. In this article I will provide a script to automate the installation of a WSL based Linux distribution.

What is Windows Subsystem for Linux?

Put simply, it is a distribution of Linux running on Windows. This isn’t Linux running on a hypervisor on Windows either but an actual Linux distribution running on the same system as Windows. Not all Linux native features have been implemented but substantial additions to WSL are being made at each OS update. The example below shows Ubuntu Linux running on WSL with byobu split into three windows running (clockwise): notepad.exe, htop, and vim.

Windows Subsystem for Linux Screenshot
Windows Subsystem for Linux Screenshot

Why Use It?

I’ve been a long-time Linux fan and have always kept some version of it on a home workstation, remote VPS, local VM (via Vagrant), or as a VM on whichever local hypervisor I happen to be managing at the time. I believe strongly in Open Source and the stellar community that drives it forward. There are also some things that are simply much easier to do from a Linux console than from Windows native tools.

But my selfish reason for using WSL on my Windows workstation this time is the desire to run Vagrant/Terraform jobs with the Ansible plugin. Ansible currently is only supported as a control node on Linux so rather than deploying a whole new VM (via Vagrant with containers of course), I’ve decided to attempt to get things working on Windows via WSL.

How do I Setup WSL?

There are two steps for setting up WSL:

  1. Enable the optional feature on your Windows 10 or 2016 OS
  2. Install a supported distribution

The first step can be done relatively quickly with the following PowerShell in an elevated prompt:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

After a restart you can move on to your Linux distribution install.

This second step is less easy to script out as most Microsoft documentation points you to manual installation of a Linux distribution through the Windows Store. But if you look a bit further into the Windows Server 2016 install directions there are some manual download links for 3 of the 5 supported distros (primarily because of a lack of a Windows Store on Server 2016). This is great, as then we have more control over where the distribution gets installed and you can use a script I wrote to automate the whole thing!

Save this script to Install-WSL.ps1 and give it a whirl. You can install SLES, openSUSE, or Ubuntu (the last one being the default).

[CmdletBinding()]
param(
    [Parameter(HelpMessage = 'Path to save and install WSL distro to.')]
    [string]$InstallPath = 'C:\WSLDistros\Ubuntu',
    [Parameter(HelpMessage = 'Distro to attempt to download and install')]
    [ValidateSet('ubuntu', 'opensuse', 'sles')]
    [string]$Distro = 'ubuntu'
)

Begin {
    $WSLDownloadPath = Join-Path $ENV:TEMP "$Distro.zip"
    $DistroURI = @{
        'ubuntu'   = 'https://aka.ms/wsl-ubuntu-1604'
        'sles'     = 'https://aka.ms/wsl-sles-12'
        'opensuse' = 'https://aka.ms/wsl-opensuse-42'
    }
    $DistroEXE = @{
        'ubuntu'   = 'ubuntu.exe'
        'sles'     = 'SLES-12.exe'
        'opensuse' = 'openSUSE-42.exe'
    }

    function Start-Proc {
        param([string]$Exe = $(Throw "An executable must be specified"),
            [string]$Arguments,
            [switch]$Hidden,
            [switch]$waitforexit)

        $startinfo = New-Object System.Diagnostics.ProcessStartInfo
        $startinfo.FileName = $Exe
        $startinfo.Arguments = $Arguments
        if ($Hidden) {
            $startinfo.WindowStyle = 'Hidden'
            $startinfo.CreateNoWindow = $True
        }
        $process = [System.Diagnostics.Process]::Start($startinfo)
        if ($waitforexit) { $process.WaitForExit() }
    }

    Function ReRunScriptElevated {
        if ( -not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator') ) {
            Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" $PSCommandArgs" -WorkingDirectory $pwd -Verb RunAs
            Exit
        }
    }

    ReRunScriptElevated
}
end {
    if ((Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux).State -ne 'Enabled') {
        try {
            Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -NoRestart
        }
        catch {
            Write-Warning 'Unable to install the WSL feature!'
        }
    }
    else {
        Write-Output 'Windows subsystem for Linux optional feature already installed!'
    }

    $InstalledWSLDistros = @((Get-ChildItem 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss' -ErrorAction:SilentlyContinue | ForEach-Object { Get-ItemProperty $_.pspath }).DistributionName)

    $WSLExe = Join-Path $InstallPath $DistroEXE[$Distro]

    if ($InstalledWSLDistros -notcontains $Distro) {
        Write-Output "WSL distro $Distro is not found to be installed on this system, attempting to download and install it now..."    

        if (-not (Test-Path $WSLDownloadPath)) {
            Invoke-WebRequest -Uri $DistroURI[$Distro] -OutFile $WSLDownloadPath -UseBasicParsing
        }
        else {
            Write-Warning "The $Distro zip file appears to already be downloaded."
        }

        Expand-Archive $WSLDownloadPath $InstallPath -Force

        if (Test-Path $WSLExe) {
            Write-Output "Starting $WSLExe"
            Start-Proc -Exe $WSLExe -waitforexit
        }
        else {
            Write-Warning "  $WSLExe was not found for whatever reason"
        }
    }
    else {
        Write-Warning "Found $Distro is already installed on this system. Enter it simply by typing bash.exe"
    }
}

There are some interesting tricks in this script that make it worth your time to pick apart (even if you don’t want to install a distro). For instance, you don’t have to run the script elevated as it will restart itself and prompt for elevation if required to do so. We also use the registry to determine the current WSL distributions that are installed.

Next Steps

Once you have your distro installed you can jump into it by running bash.exe from anywhere. Then, one of the first things I like to do is setup screen (or on Ubuntu byobu is a great wrapper for both screen and tmux) and start customizing my console experience.

Also, if you aren’t using cmder or an alternate console application I recommend getting one along with some better terminal/programmer fonts. I personally prefer the Hack font.

Check out this person’s site for a truly epic WSL customization script that includes a ton of dot sourced files, oh-my-zsh customizations, and more.

You may also be interested in setting up another Linux distribution. Well, some crazy person has already mostly figured that out as well. You can now also install Kali and Debian from the Windows store if you like. There is nothing stopping you from having multiple distros installed simultaneously. But you will have to switch the active distro with wslconfig.exe /setdefault.

I’ve also put together some scripts for transferring files back and forth between Windows and WSL (via /mnt/<drive>), running commands within WSL from Windows (via the distro installer), and for post installation tasks to get the recent versions of Hashicorp apps (like terraform, vagrant, and vault) that I’ll post in a follow up article soon.