Win32 OpenSSH Package
I've recently been using Macos at work lately in order to share admin responsibilities across the team. Still suppporting Windows, however there are a couple of tools I use to make working Windows from a Mac simpler.
PowerShell
Microsoft Open-Sourced PowerShell in 2016 and today in 2018, you can get stable installations for Macos, Linux and Windows on github which is often referred to as PSCore.
As an aside, this new version of powershell is not nativly backward compatible with compiled binary modules of the previous "Windows Powershell", however recently in development is a new module: WindowsCompatibility (currently only available on Windows Insider builds) that allows your to import "Windows Powershell" modules into PSCore.
When alpha and beta builds first became available I started testing remote sessions from Linux and Macos to Windows (As I would prefer to work from a unix system at work), but quickly found that the native "Enter-PSSession" wasn't supported from PSCore.
OpenSSH
Around the same time, Microsoft began working with the OpenBSD's OpenSSH project to bring official OpenSSH builds to Windows and the PSCore team found a way to make "Enter-PSSession" work with this.
Packaging PSCore
Packaging PSCore is very straightforward and I won't go into detail here. Suffice it to say that PSCore is released as an MSI and these are very simple to deploy using tools like Configuration Manager.
OpenSSH Package
Essentially I created a Windows Powershell script which follows the installation directions on the Win32 OpenSSH github Installation page.
The Scripts
Deploying an application using scripts in Configuration Manager, usually requires 3 scripts, and this case is no exception. I have provided the all needed scripts below:
Install.ps1
[CmdLetBinding()]
Param()
#region Helper functions
function Get-Path {
[CmdLetBinding()]
Param(
[ValidateSet(
"Machine",
"User"
)]$Context = "User",
[Switch]$Raw
)
If ($Context -eq "Machine") {
$Root = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
} else {
$Root = 'HKCU:'
}
If ($Raw){
Get-ItemPropertyValue -Path "$Root\Environment" -Name Path
} Else {
Try {
(Get-ItemPropertyValue -Path "$Root\Environment" `
-Name Path -EA SilentlyContinue) -split ';'
} Catch {
Write-Warning "No user environment variables found"
}
}
}
function Add-Path {
[CmdLetBinding()]
Param(
[Parameter(Mandatory=$True)]
[ValidateScript({
if (Test-Path -Path $_) {
$True
} else {
throw "Unable to validate path $_"
}
})]$Path,
[ValidateSet(
"Machine",
"User"
)]$Context
)
Write-Verbose "Adding $Path to environment"
if ($Context -eq 'Machine') {
If (! $Path -in (Get-Path -Context Machine)){
Write-Verbose "Adding $Path to machine context"
setx /m PATH "$(Get-Path -Context Machine -Raw);$Path"
}
} else {
Write-Verbose "Adding $Path to user context"
If (! $Path -in (Get-Path -Context User)){
Write-Verbose "Adding $Path to user context"
setx PATH "$(Get-Path -Context Use -Raw);$Path"
}
}
}
function New-SymbolicLink {
Param($Link,$Target)
If (-Not (Test-Path -Path $Link)){
If ((Get-Item $Target).PSIsContainer) {
cmd.exe /c mklink /D $Link $Target
} Else {
cmd.exe /c mklink $Link $Target
}
}
}
#endregion
# Extract OpenSSH
$Archive = Get-ChildItem -Filter *.zip
Expand-Archive -Path $Archive -DestinationPath $env:ProgramFiles
Rename-Item -Path $Env:ProgramFiles\OpenSSH-Win64 -NewName OpenSSH
#Add InstallDir to Path
Add-Path -Path $Env:ProgramFiles\OpenSSH -Context Machine -Verbose
# Configure OpenSSH
& $Env:ProgramFiles\OpenSSH\install-sshd.ps1
# Start sshd service
Start-Service -Name sshd
# Set service startup
Set-Service sshd -StartupType Automatic
Set-Service ssh-agent -StartupType Automatic
# Setup pwsh link to work around
# https://github.com/PowerShell/Win32-OpenSSH/issues/784
# Find PSCore Install and Make symbolic link
$PSCoreDir = Get-ChildItem -Path $env:ProgramFiles\PowerShell `
-Directory | Select-Object -Last 1
New-SymbolicLink -Link $env:SystemDrive\pwsh -Target $PSCoreDir.FullName
# Enable Password Authentication and set pwsh as default shell
$NewConfig = Get-Content -Path $Env:ProgramData\ssh\sshd_config |
ForEach-Object {
Switch ($_) {
{$_ -match '^#PasswordAuthentication\syes'} {$_.replace('#','')}
{$_ -match '^#PubkeyAuthentication\syes'} {$_.replace('#','')}
{$_ -match '^Subsystem\s+sftp\s+'} {
'Subsystem powershell c:\pwsh\pwsh.exe -sshs -NoLogo -NoProfile'
}
Default {$_}
}
}
# Update sshd config
Set-Content -Path $Env:ProgramData\ssh\sshd_config -Value $NewConfig `
-Force
# Restart sshd
Restart-Service sshd
Uninstall.ps1
[CmdLetBinding()]
Param()
#region Helper functions
function Remove-SymbolicLink {
Param($Link,$Target)
If (Test-Path -Path $Link){
If ((Get-Item $Target).PSIsContainer) {
cmd.exe /c rmdir $Link
} Else {
cmd.exe /c del $Link
}
}
}
function Get-Path {
[CmdLetBinding()]
Param(
[ValidateSet(
"Machine",
"User"
)]$Context = "User",
[Switch]$Raw
)
If ($Context -eq "Machine") {
$Root = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
} else {
$Root = 'HKCU:'
}
If ($Raw){
Get-ItemPropertyValue -Path "$Root\Environment" -Name Path
} Else {
Try {
(Get-ItemPropertyValue -Path "$Root\Environment" `
-Name Path -EA SilentlyContinue) -split ';'
} Catch {
Write-Warning "No user environment variables found"
}
}
}
function Remove-Path {
[CmdLetBinding()]
Param(
[Parameter(Mandatory=$True)]
$Path,
[ValidateSet(
"Machine",
"User"
)]$Context
)
Write-Verbose "Removing $Path from environment"
if ($Context -eq 'Machine') {
If ($Path -in (Get-Path -Context Machine)){
Write-Verbose "Removing $Path from machine context"
$NewPath = ""
Get-Path -Context Machine | Where-Object {
$psItem -ne $Path -and
$psItem -ne ""
} ForEach-Object {
$NewPath += "$psItem;"
}
setx /m PATH "$NewPath"
}
} else {
Write-Verbose "Removing $Path from user context"
If ($Path -in (Get-Path -Context User)){
Write-Verbose "Removing $Path from user context"
$NewPath = ""
Get-Path -Context User | Where-Object {
$psItem -ne $Path -and
$psItem -ne ""
} ForEach-Object {
$NewPath += "$psItem;"
}
setx PATH "$NewPath"
}
}
}
#endregion
& $Env:ProgramFiles\OpenSSH\uninstall-sshd.ps1
# Extract OpenSSH
Remove-Item -Path $env:ProgramFiles\OpenSSH -Recurse -Force
Remove-Path -Path $env:ProgramFiles\OpenSSH -Context Machine -Verbose
# Find PSCore Install and remove symbolic link
$PSCoreDir = Get-ChildItem -Path $env:ProgramFiles\PowerShell -Directory | Select-Object -Last 1
Remove-SymbolicLink -Link $env:SystemDrive\pwsh -Target $PSCoreDir.FullName
# Remove old config
Remove-Item -Path $env:ProgramData\ssh -Recurse -Force
Detect.ps1
$AssumeInstalled = $True
If (-Not (Test-Path $Env:ProgramFiles\OpenSSH)) {
$AssumeInstalled = $False
}
If (-Not (Test-Path $Env:SystemDrive\pwsh)) {
$AssumeInstalled = $False
}
If (-Not (Get-Service sshd -ErrorAction SilentlyContinue)) {
$AssumeInstalled = $False
}
If ($AssumeInstalled) {
Write-Output "True"
}
Using OpenSSH with Powershell
Now that I have used these scripts to deploy OpenSSH and PSCore, I can PSRemote to a PC using my Mac.
The old way to use "Enter-PSSession" was by specifying the ComputerName parameter like so:
PS\> Enter-PSSession -ComputerName Blah
However, when using OpenSSH with a PS Session you do the following:
PS\> Enter-PSSession -HostName Blah -UserName MrBlah
You could also setup a session in a variable and resue it multiple times in a session:
PS\> $s = New-PSSesssion -HostName Blah -UserName MrBlah
PS\> Enter-PSSession -Session $s
[Blah] PS\>
I hope you have found this usefull.
Tags: powershell, pscore, openssh, windows, macos
How I Code
Updated 17/08/2018
Coding can be fun. I've enjoyed coding from a young age, starting with GW-Basic at maybe 6, 7, or 8.
I remember my brother Alex seemed like a real genius with the computer (an IBM clone made by Acer 8086 XT). Using Basic he could make the computer do anything and was writing his own games.
Back then, editing code we would laugh at today and we take the humble text editor for granted. Take this example, you would type list to see your code:
>LIST
10 PRINT "WELCOME TO JESSES GAME"
20 PRINT "ENTER YOUR NAME"
30 $I = INPUT
40 PRINT "WELCOME $I, STRAP YOURSELF IN"
To edit a line of code you would re-write it by typing it in, line number and all.
20 PRINT "ENTER YOUR FULL NAME"
And to insert a line, start a line with a number between existing lines
31 $A=$I
When you ran out of in-between-lines there was a command you could run to reindex your lines which would space them all out 10 between each other.
Anyhow, when my Dad was about the same age as I am now (35), he went back to University to study Computer Science. I remember him bringing home Slackware and RedHat on floppies, which we would install and he would give me lessons on using Vi possibly vim, but I didn't know at the time. (This is probably around 1996).
Since finishing School and entering the workforce I have mostly worked in Windows environments. Even still, with the occasionaly need to touch GNU/Linux at work and often testing Distro's at home I would always feel more efficient when using Vi/m.
My feeling when using another editor is that moving around and changing text feels so lethargic when done one button at a time. This drove me in recent years to keep a copy in my home profile.
Around 2011 I switched from VBScript and the occasional perl script to writing fulltime in Powershell, so it made sense to try a few different editors which are more native to the Windows platform. I tried Visual Studio Code, Powershell ISE, Notepad++ and still kept coming back to vim.
Visual Studio Code is a great alternative, and it's Powershell extensions are very good. Hoever being an electron app, it suffers from performance and memory consumption issues. I love squeezing every drop of battery out of my PC and when you see 7mb RAM on Vim vs 500Mb+ on VSCode, you might rethink your choices.
Therefore I've resorted to delving into the world of customizing vim and setting up plugins.
One of the main things I'm trying to acheive is a cross platform configuration. You see, at work I'm on Windows and MacOs and at home I'm on Gentoo Linux. So I have written my .vimrc file to work on any platform. I usually sync it with OneDrive for Business and symlink it into my linux/mac/Windows home directory with a seperate setup script. Without further ado, here it is with some comments
.vimrc
if has("win32") " Check if on windows \
" Also supports has(unix)
source $VIMRUNTIME/mswin.vim " Load a special vimscript \
" ctrl+c and ctrl+v support
behave mswin " Like above
set ff=dos " Set file format to dos
let os='win' " Set os var to win
set noeol " Don't add an extra line \
" at the end of each file
set nofixeol " Disable the fixeol : Not \
" not sure why this is needed
set backupdir=~/_vimtmp,. " Set backupdir rather \
" than leaving backup files \
" all over the fs
set directory=~/_vimtmp,. " Set dir for swp files \
" rather than leaving files \
" all over the fs
set undodir=$USERPROFILE/vimfiles/VIM_UNDO_FILES " Set persistent undo\
" files
" directory
let plug='$USERPROFILE/.vim' " Setup a var used later to \
" store plugins
set shell=powershell " Set shell to powershell \
" on windows
set shellcmdflag=-command " Arg for powrshell to run
else
set backupdir=~/.vimtmp,.
set directory=~/.vimtmp,.
set undodir=$HOME/.vim/VIM_UNDO_FILES
let uname = system('uname') " Check variant of Unix \
" running. Linux|Macos
if uname =~ "Darwin" " If MacOS
let plug='~/.vim'
let os='mac' " Set os var to mac
else
if isdirectory('/mnt/c/Users/jpharris')
let plug='/mnt/c/Users/jpharris/.vim'
let os='wsl'
else
let plug='~/.vim'
let os='lin'
endif
endif
endif
execute "source " . plug . "/autoload/plug.vim"
if exists('*plug#begin')
call plug#begin(plug . '/plugged') " Enable the following plugins
Plug 'tpope/vim-fugitive'
Plug 'junegunn/gv.vim'
Plug 'junegunn/vim-easy-align'
Plug 'jiangmiao/auto-pairs'
"Plug 'vim-airline/vim-airline' " Airline disabled for perf
Plug 'morhetz/gruvbox'
Plug 'ervandew/supertab'
Plug 'tomtom/tlib_vim'
Plug 'MarcWeber/vim-addon-mw-utils'
Plug 'PProvost/vim-ps1'
Plug 'garbas/vim-snipmate'
Plug 'honza/vim-snippets'
call plug#end()
endif
" Remove menu bars
if has("gui_running") " Options for gvim only
set guioptions -=m " Disable menubar
set guioptions -=T " Disable Status bar
set lines=50 " Set default of lines
set columns=80 " Set default of columns
if os =~ "lin"
set guifont=Fira\ Code\ 12
elseif os =~ "mac"
set guifont=FiraCode-Retina:h14
else
set guifont=Fira_Code_Retina:h12:cANSI:qDRAFT
set renderoptions=type:directx
set encoding=utf-8
endif
set background=dark
colorscheme gruvbox
else
set mouse=a
if has('termguicolors')
set termguicolors " Enable termguicolors for \
" consoles which support 256.
set background=dark
colorscheme gruvbox
endif
endif
if has("persistent_undo")
set undofile " Enable persistent undo
endif
colorscheme evening " Set the default colorscheme
" Attempt to start vim-plug
syntax on " Enable syntax highlighting
filetype plugin indent on " Enable plugin based auto \
" indent
set tabstop=4 " show existing tab with 4 \
" spaces width
set shiftwidth=4 " when indenting with '>', \
" use 4 spaces width
set expandtab " On pressing tab, insert 4 \
" spaces
set number " Show line numbers
" Map F5 to python.exe %=current file
nnoremap <silent> <F5> :!clear;python %<CR>
" Remap tab to auto complete
imap <C-@> <C-Space>
" Setup ga shortcut for easyaline in visual mode
nmap ga <Plug>(EasyAlign)
" Setup ga shortcut for easyaline in normal mode
xmap ga <Plug>(EasyAlign)"