Can you use Linux commands in Powershell?

Today we discuss one of a few questions a lot of sysadmins and IT Admins ask?

Can you use Linux commands in Powershell?

The Windows Subsystem for Linux (WSL) was a big step forward in this regard, allowing developers to call Linux commands from Windows via wsl.exe (e.g. wsl ls). While an improvement, the experience falls short in various ways:

  1. It’s inconvenient and strange to use wsl to prefix commands.
  2. WSL login profiles with aliases and environment variables do not honour default parameters.
  3. Backslashes are sometimes misinterpreted as escape characters rather than directory separators in Windows paths supplied as arguments.
  4. Because Windows paths aren’t always translated to the correct mount point within WSL, they don’t always resolve.
  5. Path completion is not available on Linux.
  6. The completion of commands is not supported.
  7. The completion of arguments is not supported.

As a result of these flaws, Linux commands appear to Windows as second-class citizens and are more difficult to use than they should be. These difficulties must be addressed in order for a command to feel like a native Windows command.

What are Wrappers for PowerShell functions?

With PowerShell function wrappers, we can eliminate the need to prefix commands with wsl, handle the conversion of Windows paths to WSL paths, and allow command completion. The wrappers’ essential requirements are as follows:

Each Linux command should have one function wrapper with the same name as the command.

The wrapper should be able to recognise and translate Windows paths supplied as arguments into WSL paths.

The wrapper should call wsl with the appropriate Linux command, providing any pipeline input and any command line arguments to the function.

We can abstract the design of these wrappers and build them dynamically from a list of commands to import because this template may be applied to any command.

 

# The commands to import.
$commands = "awk", "emacs", "grep", "head", "less", "ls", "man", "sed", "seq", "ssh", "tail", "vim"# Register a function for each command.
$commands | ForEach-Object { Invoke-Expression @"
Remove-Alias $_ -Force -ErrorAction Ignore
function global:$_() {
for (`$i = 0; `$i -lt `$args.Count; `$i++) {
# If a path is absolute with a qualifier (e.g. C:), run it through wslpath to map it to the appropriate mount point.
if (Split-Path `$args[`$i] -IsAbsolute -ErrorAction Ignore) {
`$args[`$i] = Format-WslArgument (wsl.exe wslpath (`$args[`$i] -replace "\\", "/"))
# If a path is relative, the current working directory will be translated to an appropriate mount point, so just format it.
} elseif (Test-Path `$args[`$i] -ErrorAction Ignore) {
`$args[`$i] = Format-WslArgument (`$args[`$i] -replace "\\", "/")
}
}if (`$input.MoveNext()) {
`$input.Reset()
`$input | wsl.exe $_ (`$args -split ' ')
} else {
wsl.exe $_ (`$args -split ' ')
}
}
"@
}

The $command above code will list/ specifies which commands should be imported. Then, using the Invoke-Expression command, we dynamically build the function wrapper for each (first removing any aliases that would conflict with the function).

The function goes through the command line parameters, identifying Windows paths with the Split-Path and Test-Path commands, and then converting them to WSL paths. We pass the paths through Format-WslArgument, a helper function that we’ll build later to escape special characters like spaces and parentheses that might otherwise be misconstrued.

Finally, we send wsl pipeline input as well as any command line parameters.

With these function wrappers in place, we can now call our favourite Linux commands without needing to prefix them with wsl or worry about how they’ll be executed.

What about default parameter we use in Linux?

In Linux, it’s customary to utilise login profiles to specify aliases and/or environment variables to set default parameters for programmes you use regularly (for example, alias ls=ls -AFh or export LESS=-i). One disadvantage of using wsl.exe to proxy through a non-interactive shell is that no login profiles are loaded, therefore these default options are unavailable (i.e. ls within WSL and wsl ls would behave differently with the alias defined above).

$PSDefaultParameterValues is a common technique for defining default parameter values in PowerShell, although it is only available for cmdlets and advanced functions. It’s possible to turn our function wrappers into advanced functions, but there are certain drawbacks (for example, PowerShell matches partial parameter names (like -a for -ArgumentList), which would conflict with Linux commands that take partial names as arguments).

We can allow default parameters for Linux commands with a little tweak to our function wrappers, similar to $PSDefaultParameterValues!

 

function global:$_() {`$defaultArgs = ((`$WslDefaultParameterValues.$_ -split ' '), "")[`$WslDefaultParameterValues.Disabled -eq `$true]
if (`$input.MoveNext()) {
`$input.Reset()
`$input | wsl.exe $_ `$defaultArgs (`$args -split ' ')
} else {
wsl.exe $_ `$defaultArgs (`$args -split ' ')
}}
You can now add statements like the ones below to your PowerShell profile to specify default parameters by passing $WslDefaultParameterValues down into the command line we transmit through wsl.exe!
$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first"

Conclusion:

We can incorporate Linux commands into Windows as though they were native applications using PowerShell and WSL. There’s no need to look for Win32 versions of Linux utilities or interrupt your work to enter a Linux shell. Simply download WSL, create a PowerShell profile, then specify the commands you want to import. Even native Windows commands don’t give the extensive argument completion displayed here for both command parameters and Linux and Windows file paths.

https://github.com/mikebattista/PowerShell-WSL-Interop has the whole source code as well as extra instructions for implementing it into your workflow.

Which Linux commands are the most beneficial to you? What additional aspects of your developer workflow are you unhappy with?

 

Citations/Links used in article are:

  1. https://docs.microsoft.com/en-us/windows/wsl/about
  2. https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parameters_default_values?view=powershell-6
  3. https://github.com/mikebattista/PowerShell-WSL-Interop

Any questions or new topics to discuss please comment on this post… 🙂

Leave a Reply

Your email address will not be published.