There are three primary toolsets that have helped me to get comfortable with Windows 10 as a developer coming from MacOS.

  1. WSL (and the tmux, bash, powershell, clipboard integration)
  2. AutoHotKey
  3. Docker

Note: In order to get the full benefit of WSL, you may want to join the Windows Insiders program. That will get you early access to WSL improvements (the most important of which are fixes that improve tmux compatability), and while it does expose you to more bugs and means you’ll be doing free beta testing for Microsoft I’ve found it to be worthwhile.

WSL

So what is WSL? It’s a “Linux userspace” for Windows, which translates into being able to run linux programs in bash on a terminal. It’s not a full linux installation, but you get most of the linux utilities and workflow advantages that makes linux a great platform for developers while also getting access to the large library of Windows programs.

With WSL you can run Windows binaries from bash. So to jump right into some examples, instead of running this familiar command in Terminal in MacOS to get content from a file into your clipboard:

cat myfile.txt | pbcopy

Use can use clip.exe from the WSL terminal emulator instead:

cat myfile.txt | clip.exe

To stay on the topic of the clipboard, make sure you install an X Server on Windows so you can connect to it from your WSL run programs, like vim.

I installed VcXsrv and then in my .bash_profile I set the X variable DISPLAY to localhost:

# windows machine with wsl, pair this with running vcxsrv with clipboard enabled
export DISPLAY=localhost:0.0

Now when I’m scrolling thorugh text in vim on the WSL terminal emulator and I need to yank a line of text, it’s magically on my Windows clipboard!

Powershell Integration

You will occassionally still need to use Powershell for accessing the win32 API, here’s some examples to get you started.

Powershell’s answer to something like .bash_profile:

File: /c/Users/jharding/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1

# directory where my scripts are stored
$psdir="C:\windows_scripts\"
# load all 'autoload' scripts
# Get-ChildItem "${psdir}\*.ps1" | %{.$_}
# load just one profile
Get-ChildItem "${psdir}\profile.ps1" | %{.$_}

Now on new powershell windows I’ll have access to these functions:

File: /c/windows_scripts/profile.ps1

Write-Host "Custom PowerShell Environment Loaded"

Set-PSReadlineOption -BellStyle None

function ppw { python.exe -c "import sys; print('\n'.join(sys.path))" }

function start_ubuntu { C:\Program` Files\Oracle\VirtualBox\VBoxManage.exe startvm "ubuntu" --type headless }
function start_centos { C:\Program` Files\Oracle\VirtualBox\VBoxManage.exe startvm "centos" --type headless }
function windows_find { dir -Path . -Filter @args -Recurse -ErrorAction SilentlyContinue -Force | %{$_.FullName} }

function  for_each_service ($command, $prefix, [string[]]$names) {
  For ($i=0; $i -lt $names.Length; $i++) {
    echo "$command $prefix$($names[$i])"
    sc.exe $command "$prefix$($names[$i])"
    #if($i -eq 0) {
      Start-Sleep -s 1
    #}
  }
}

# stop_stack MyPrefix
function stop_stack ($prefix) {
  $names = @("XService", "YService")
  for_each_service "stop" $prefix $names
}

Sometimes you might want to invoke a powershell script from bash, you can do something like this:

alias windows_arrange='powershell.exe -File "C:\windows_scripts\arrange.ps1"'

In the above example script I’m actually invoking AutoHotKey.exe:

C:\Program` Files\AutoHotkey\AutoHotkey.exe C:\windows_scripts\Arrange.ahk

TMUX

Here’s my tmux config for WSL. The tmux-yank plugin is useful for keeping yank clipboard integration even between different operating systems. Using tmux allows you to open a powershell session and take advantage of scrolling and copy and paste which is usually pretty difficult if you’re using the native windows powershell terminal.

# increase the history limit on scrollback
set-option -g history-limit 40000

# use 256 colors 
set -g default-terminal "screen-256color"

# add UTC time to right status
set -g status-right "#[fg=black]#(TZ='US/Central' date +%%H:%%M) Central| #(TZ='US/Eastern' date +%%H:%%M) Eastern| #(TZ=UTC date -u +%%H:%%M) UTC"

# Use vim keybindings in copy mode
setw -g mode-keys vi

# refresh tmux config for easier tmux debugging
bind-key R source-file ~/.tmux.conf \; display-message "tmux.conf reloaded."

# alerts when output changes in other windows - kind of useful for ansible jobs?
set -g visual-activity on
setw -g monitor-activity on

# remap prefix to Control + a
set -g prefix C-a
# bind 'C-a C-a' to type 'C-a'
bind C-a send-prefix
unbind C-b

bind-key C-l send-keys 'C-l' \; clear-history
bind -n C-k clear

set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tmux-yank'

# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'

AutoHotKey

To ease the transition from MacOS I’ve scripted some of my favorite keyboard shortcuts from MacOS into Windows using AutoHotKey.

For example, I found the tab switching shortcut on Windows to be ridiculously unintuitive: Ctrl + Tab to switch to the right, Ctrl + Shift + Tab to move to the left. In the following AutoHotKey I change that to match what it is on MacOS and also added a screengrab shortcut.

File: /c/windows_scripts/conf.ahk

; chrome - use mac style tab switching
#IfWinActive, ahk_class Chrome_WidgetWin_1
!+[::SendInput, ^{PGUP}
!+]::SendInput, ^{PGDN}
#IfWinActive

; chrome - use mac style back,forward navigation
#IfWinActive, ahk_class Chrome_WidgetWin_1
![::Send {Browser_Back}
!]::Send {Browser_Forward}
#IfWinActive

;If Snipping Tool window is active ESC will reload the script 
#IfWinActive Snipping Tool
Escape::Reload
#IfWinActive ;The Hotkey is defined below, Change it as you like.
#+s::
Run C:\Windows\system32\SnippingTool.exe
WinWaitActive,Snipping Tool
Send !nr 
return

Docker

For docker I followed Nick Janetakis’ guide here which makes it easy to keep working in Linux space when working with Docker containers.

Unfortunately you still have to deal with the Windows restriction on using VirtualBox and Docker at the same time (something to do with Windows’ virtualization stack Hyper-V) but at least when you do want to use Docker you can use it without need to touch Windows GUIs.

October 14th, 2020 Update

I decided to stop using WSL. Not only did WSL fork (there is now a version 1 and version 2 where version 2 is not strictly an upgrade) but I found too many issues around file permissions where my linux environment was accessing Windows files and performance. In fact performance accessing the disk was the main reason I stopped using WSL.

Now I use a Centos 7 linux VM running on VirtualBox, primarily controlled via Vagrant. For file sharing I use samba mounts, and for clipboard sharing I use VcXsrv with X11 forwarding when I connect to the VM. So far this has been a strict upgrade and I like knowing I’m using a “real” operating system.

I also stopped using Docker on Windows. This was due mostly to my preference for Vagrant, since the Windows version of Docker requires Hyper-V and you can’t have both Hyper-V enabled and run VirtualBox, and that containers are not widely used at my company so there really wasn’t any need for it.

I’m not sure I’d say I prefer this new setup to my personal MacBook Pro (apart from the Dell XPS 15 keyboard, which is much nicer than my MBP 2014 keyboard) but for my company’s software ecosystem (which supports only Windows and Linux) this setup has been pretty good.