Search code examples
powershellwindows-subsystem-for-linux

Control WSL installation from powershell script


I'm trying to write a script which will install WSL2 Ubuntu for a user and then perform other things on the windows os

In theory, this should be as simple as running

wsl --install -d Ubuntu

The problem is that after executing the above command powershell immediately reports "operation successfull" and my script continues to run, while the installation itself is being performed in another window (user need to provide username and pass during first launch of WSL).

What I want to achieve is that my script should wait for the WSL window to close before contiuing.

So far I've tried

$wslInstall = Start-Process -FilePath wsl.exe -ArgumentList "--install -d Ubuntu" -Wait -PassThru
$wslInstall.WaitForExit()

but it doesn't work as I've imagined, the script continues to run as if the newly created process is not a child process of the installation.


Solution

  • I can reproduce this. The problem here is that wsl --install -d Ubuntu is a two-step process:

    • First, the Ubuntu application package is installed from the Microsoft Store. This application package includes the root filesystem. You can see the location (and rootfs package) by running the following as Administrator from PowerShell:

      Get-ChildItem -Recurse 'C:\Program Files\WindowsApps\' | Where-Object {$_.Name -eq 'install.tar.gz' }
      
    • The second step is turning this root filesystem into an actual distribution by (roughly):

      • Extracting the file system
      • Importing it into a WSL 1 (overlay) or 2 (virtual SDD) filesystem.
      • Creating the default user and password
      • Registering the distribution with WSL

    The problem that you are facing is that, once the "Package installation" has completed, the first stage launches the second stage, but it doesn't wait on it itself. So the first stage completes and returns control. The -wait just isn't going to work since the first stage isn't waiting anyway.

    I'm fairly sure that I understand what you are trying to do, but you may have to take a more "manual" approach to installing if you want full control over each step. Fortunately, WSL include the --install --no-launch argument to only run the first stage (the package install). Side-note: I'm not sure what WSL releases have this argument available. It may be a more recent addition, but it should be available to all users at this point if using the latest WSL.

    The second stage can then be kicked off with ubuntu.exe.

    If this installer is running from a terminal session already, then it's best to run Stage 2 directly. Otherwise, you can start it with the same start-process as above.

    Option 1, from an existing terminal:

    $wslInstall = Start-Process -FilePath wsl.exe -ArgumentList "--install -d Ubuntu --no-launch" -Wait -PassThru
    # Check for success using $wslInstall
    ubuntu.exe
    
    # User will be asked for username and password, then be dropped into Ubuntu.
    # They will have to know to exit WSL in order to continue your install.
    # After exiting, check for success using $LASTEXITCODE
    

    Option 2, from a non-terminal installer:

    $wslInstall = Start-Process -FilePath wsl.exe -ArgumentList "--install -d Ubuntu --no-launch" -Wait -PassThru
    # Check for success using $wslInstall
    
    $ubuntuInstall = Start-Process -FilePath ubuntu.exe -Wait -PassThru
    
    # User will be asked for username and password, then be dropped into Ubuntu.
    # They will have to know to exit WSL in order to continue your install.
    # After exiting, check for success using $ubuntuInstall
    

    If that's going to be confusing for the user to have to create the username/password and then know to exit Ubuntu to continue, then you have a third-option, that I'll just summarize:

    • Run the first stage install (--no-launch) as above.
    • wsl --import the install.tar.gz file mentioned above into a distribution. Use a name other than Ubuntu or one of the "standard" distribution names.
    • Use a wsl --exec <script> that asks for the username/password (securely) and creates the user with the appropriate permissions.
    • Create a /etc/wsl.conf to set the default user per my SU answer. There's also a comment under there about creating the user manually (and I have another post somewhere, I believe Ask Ubuntu, on the topic).

    Side-note ...

    the above command powershell immediately reports "operation successfull" and my script continues to run

    If it is returning immediately, then my guess is that the Ubuntu "application package" is still installed. Remember when testing this to uninstall completely. That's going to require two steps:

    • Uninstall the application package from Add or remove programs (or right click on "Ubuntu" in the Start menu and select Uninstall).
    • wsl --unregister <distro> the partially or fully installed distribution