The other day I read an interesting article on Adam Bertram's blog about editing files with a text editor in PowerShell. Naturally, the PowerShell wheels in my head began turning. While I was intrigued by some of the options in the article, I've in fact installed the Micro editor to play with, I realized I already had my favorite console text editor installed. The article mentioned installing the Nano editor for Windows. But I have a WSL installation of Ubuntu. I already have the nano editor. To be fair, I installed Nano for Windows but it is just different enough from the Linux version to annoy me. Why not make it easier to use what I already have in my PowerShell console? Let's have some fun and maybe learn something new.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
WSL Commands
If you have a Linux distribution installed, you can run native Linux commands from your PowerShell prompt.
Typically, these commands are running in a Linux shell but I'm getting the output in my PowerShell console. Regardless, I can easily launch the nano editor directly from my PowerShell prompt.
wsl nano
Converting Paths
But here's where it gets tricky. I want to be able to launch the nano editor and open a text file from Windows. In Linux, I can run a command like this:
nano /home/jeff/file.txt
If I try something like this from my PowerShell prompt:
wsl nano c:\work\fun.ps1
The nano editor will open but think I am creating a new file. That's because nano is really running in the Ubuntu distribution, not my PowerShell prompt. Luckily, all of my local drives are automatically mounted in WSL. If I convert the path in my head, this will work.
wsl nano /mnt/c/work/fun.ps1
Of course, I want things to be simple and easy so I'll need a function to convert a Windows path to the WSL mounted path. First, I should convert the Windows path to a true file system path. This will help convert paths like .\file.txt or where I'm using a PSDrive like S:\remote.ps1.
$cPath = Convert-Path -Path $Path
I'm then going to split this converted path into the parent directory and file name.
$file = Split-Path -Path $cpath -Leaf
$dir = Split-Path -Path $cPath -Parent
Next, I want the path portion of the parent directory without the drive letter.
$folder = $dir.Substring(3)
I am doing all of this because I need to build the corresponding path in Linux using the mount point.
"/mnt/{0}/{1}/{2}" -f $dir[0].tostring().ToLower(), $folder.replace("\", "/"), $file
I'm building a new string using the first letter of the parent directory. Because Linux is case-sensitive I'm converting it to lowercase to match the path in Linux. The other accommodation I have to make is to switch all the \ to /.
Now the nano command works as expected.
Building a Tool Set
Of course, I also built a PowerShell function to launch nano, taking into account if I wanted to edit an existing file or create something new.
[string]$cmd = "wsl --exec nano"
if ($Path) {
$wslPath = ConvertTo-WSLPath -Path $Path
$cmd += " $wslPath"
}
#convert to a scriptblock
$sb = [scriptblock]::Create($cmd)
Invoke-Command -ScriptBlock $sb
This is the core of the function.
The last pieces that I added were related to validation. My ConvertTo-WSLPath and Invoke-Nano functions are in a .ps1 file which I will have to dot source. But the commands can't be used if there is no WSL installation
$WslTest = ((wsl -l).split()).where({$_.length -gt 1}) -contains "(Default)"
If ( -Not $WslTest) {
Write-Warning "These commands require a WSL installation"
Return
}
Figuring this out took way more effort than I imagined. You can run wsl -l to see installed installations. However, the encoding of the output isn't necessarily what you see in the console. My efforts to use -Match or Select-String kept failing. Eventually, I came up with this code to split the results of the wsl command into an array of strings with a length greater than 1. Then I can use -Contains looking for '(Default)'. If the string isn't found, I'll display a warning and then bail out of the script.
I also wanted to verify that whoever is dot sourcing the script is running in Windows, either PowerShell 7 or Windows PowerShell. My functions are defined inside an If statement.
if ($IsWindows -OR ($PSEdition -eq 'desktop')) {
#define functions
}
Else {
Write-Warning "These commands require a Windows platform"
}
If the PowerShell console is anything else, then another warning is displayed. Want to see the final result?
# requires -version 5.1
#launch the nono text editor from a WSL installation like Ubuntu
# or install the Windows version https://sourceforge.net/projects/nano-for-windows/
# or https://sourceforge.net/projects/micro-for-windows/
# https://micro-editor.github.io/
if ($IsWindows -OR ($PSEdition -eq 'desktop')) {
#test if there is a default WSL installation
$WslTest = ((wsl -l).split()).where({$_.length -gt 1}) -contains "(Default)"
If ( -Not $WslTest) {
Write-Warning "These commands require a WSL installation"
Return
}
# a helper function to convert a Windows path to the WSL equivalent
Function ConvertTo-WSLPath {
[cmdletbinding()]
[outputtype("String")]
Param(
[Parameter(Position = 0, Mandatory, HelpMessage = "Enter a Windows file system path")]
[ValidateNotNullorEmpty()]
[string]$Path
)
Write-Verbose "[$($myinvocation.mycommand)] Starting $($myinvocation.mycommand)"
Write-Verbose "[$($myinvocation.mycommand)] Converting $Path to a filesystem path"
$cPath = Convert-Path -Path $Path
Write-Verbose "[$($myinvocation.mycommand)] Converted to $cpath"
$file = Split-Path -Path $cpath -Leaf
$dir = Split-Path -Path $cPath -Parent
$folder = $dir.Substring(3)
"/mnt/{0}/{1}/{2}" -f $dir[0].tostring().ToLower(), $folder.replace("\", "/"), $file
Write-Verbose "[$($myinvocation.mycommand)] Ending $($myinvocation.mycommand)"
}
#launch the nano editor from the WSL installation
Function Invoke-Nano {
[CmdletBinding()]
[outputtype("none")]
[alias("nano")]
Param(
[Parameter(Position = 0, HelpMessage = "Specify a text file path.")]
[ValidatenotNullorEmpty()]
[ValidateScript({ Test-Path $_ })]
[string]$Path
)
Write-Verbose "[$($myinvocation.mycommand)] Starting $($myinvocation.mycommand)"
[string]$cmd = "wsl --exec nano"
if ($Path) {
Write-Verbose "[$($myinvocation.mycommand)] Convert $Path"
$wslPath = ConvertTo-WSLPath -Path $Path
$cmd += " $wslPath"
}
Write-Verbose "[$($myinvocation.mycommand)] Using command expression $cmd"
#convert to a scriptblock
$sb = [scriptblock]::Create($cmd)
Write-Verbose "[$($myinvocation.mycommand)] Launch nano from the WSL installation"
Invoke-Command -ScriptBlock $sb
Write-Verbose "[$($myinvocation.mycommand)] Ending $($myinvocation.mycommand)"
}
}
Else {
Write-Warning "These commands require a Windows platform"
}
I want to point out something new I'm trying with my Verbose statements. I often have functions calling other functions. In each verbose statement, I'm including the command name. This makes it easier to track the flow.
Now, I can quickly edit files directly from the PowerShell console.
And no, this doesn't replace the functionality I'd get from using VS Code or the PowerShell ISE. But sometimes I just want to make a quick edit or perhaps view source code. I'm partial to the nano editor, but you could use these concepts to launch vim or any other text editor you have installed in your WSL installation.
Have fun.
This is something Windows has always missed. I still dont understand why there is no native application to edit text file in command line interface.
I ended up using neovim (fork of vim) as my default cli editor. Works with windows and linux alike. Its locally installed and quick.
Calling WSL to run editor sounds fun, i am gonna try this. Thank you.