Scott Hanselman

How to SSH into a Windows 10 Machine from Linux OR Windows OR anywhere

March 10, '20 Comments [12] Posted in Linux | Open Source | Win10
Sponsored By

I've been shushing all over the place lately. I SSH into Linux from Windows using the built-in OpenSSH Client that Windows 10 has shipped for years that you didn't know about. ;) You don't need Putty to SSH with Windows (unless it makes you happy, then putty on, my friend.)

Adding OpenSSH Server to Windows

From an Administrative PowerShell I'll see what OpenSSH stuff I have enabled. I can also do this with by typing "Windows Features" from the Start Menu.

> Get-WindowsCapability -Online | ? Name -like 'OpenSSH*'

Name : OpenSSH.Client~~~~0.0.1.0
State : Installed

Name : OpenSSH.Server~~~~0.0.1.0
State : NotPresent

Looks like I have the OpenSSH client stuff but not the server. I can SSH from Windows, but not to.

I'll add it with a similar command with the super weirdo but apparently necessary version thing at the end:

Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

Starting SSHD on Windows as a Service

Once this has finished (and you can of course run this with OpenSSH.Client as well to get both sides if you hadn't) then you can start the SSH server (as a Windows Service) with this, then make sure it's running.

Start-Service sshd
Get-Service sshd

Since it's a Windows Service you can see it as "OpenSSH SSH Server" in services.msc as well as set it to start automatically on Startup if you like. You can do that again, from PowerShell if you prefer

Set-Service -Name sshd -StartupType 'Automatic'

Remember that we SSH over port 22 so you'll have a firewall rule incoming on 22 at this point. It's up to you to be conscious of security. Maybe you only allow SSHing into your Windows machine with public keys (no passwords) or maybe you don't mind. Just be aware, it's on you, not me.

Now, from any Linux (or Windows) machine I can SSH into my Windows machine like a pro! Note I'm using the .local domain suffix to make sure I don't get a machine on my VPN (staying in my local subnet)

$ ssh scott@ironheart.local
Microsoft Windows [Version 10.0.19041.113]
(c) 2020 Microsoft Corporation. All rights reserved.

scott@IRONHEART C:\Users\scott>pwsh
PowerShell 7.0.0
Copyright (c) Microsoft Corporation. All rights reserved.

https://aka.ms/powershell
Type 'help' to get help.

Loading personal and system profiles took 1385ms.
⚡ scott@IRONHEART>

Note that when I SSH'ed into Windows I got the default cmd.exe shell. Remember also that there's a difference between a console, a terminal, and a shell! I can ssh with any terminal into any machine and end up at any shell. In this case, the DEFAULT was cmd.exe, which is suboptimal.

Configuring the default shell for OpenSSH in Windows

On my server (the Windows machine I'm SSHing into) I will set a registry key to set the default shell. In this case, I'll use open source cross platform PowerShell Core. You can use whatever makes you happy.

New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Program Files\PowerShell\7\pwsh.exe" -PropertyType String -Force

Now when I ssh into my Windows machine from elsewhere (even my iPad!) I get the shell I want:

$ ssh scott@ironheart.local
PowerShell 7.0.0
Copyright (c) Microsoft Corporation. All rights reserved.

https://aka.ms/powershell
Type 'help' to get help.

Loading personal and system profiles took 1854ms.
⚡ scott@IRONHEART>

Even better if I wanted to add a menu item (profile) to my Windows Terminal with an entry for my Windows Machine that would automatically log me into it from elsewhere using public keys, I could do that also!

Additionally, now that this is set up I can use WinSCP (available on the Window Store) as well as scp (Secure Copy) to transfer files.

Of course you can also use WinRM or PowerShell Remoting over SSH but for my little internal network I've found this mechanism to be simple and clean. Now my shushing around is non-denominational!


Sponsor: Have you tried developing in Rider yet? This fast and feature-rich cross-platform IDE improves your code for .NET, ASP.NET, .NET Core, Xamarin, and Unity applications on Windows, Mac, and Linux.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Adding a git commit hash and Azure DevOps Build Number and Build ID to an ASP.NET website

March 6, '20 Comments [15] Posted in ASP.NET | DotNetCore
Sponsored By

imageA few months back I moved my CI/CD (Continuous Integration/Continuous Development) to Azure DevOps for free. You get 1800 build minutes a month FREE and I'm not even close to using it with three occasionally-updated sites building on it. Earlier this week I wrote about making a cleaner and more intentional azure-pipelines.yml for an ASP.NET Core Web App

I was working/pairing with Damian today because I wanted to get my git commit hashes and build ids embedded into the actual website so I could see exactly what commit is in production.

That's live on hanselminutes.com righ tnow and looks like this

© Copyright 2020, Scott Hanselman. Design by @jzy, Powered by .NET Core 3.1.2 and deployed from commit 6b48de via build 20200310.7

There's a few things here and it's all in my ASP.NET Web App's main layout page called _layout.cshtml. You can look all about ASP.NET Core 101, .NET and C# over at https://dot.net/videos if you'd like. They've lovely videos.

My website footer has git commits

So let's take this footer apart, shall we?

<div class="copyright">&copy; Copyright @DateTime.Now.Year, 
<a href="https://www.hanselman.com">Scott Hanselman</a>.
Design by <a href="http://www.8164.org/">@@jzy</a>,
Powered by @System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription
and deployed from commit <a href="https://github.com/shanselman/hanselminutes-core/commit/@appInfo.GitHash">@appInfo.ShortGitHash</a>
via build <a href="https://dev.azure.com/hanselman/Hanselminutes%20Website/_build/results?buildId=@appInfo.BuildId&view=results">@appInfo.BuildNumber</a>
</div>

First, the obvious floating copyright year. Then a few credits that are hard coded.

Next, a call to @System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription which gives me this string ".NET Core 3.1.2" Note that there was a time for a while where that Property was somewhat goofy, but no longer.

I have two kinds of things I want to store along with my build artifact and output.

  • I want the the Git commit hash of the code that was deployed.
    • Then I want to link it back to my source control. Note that my site is a private repo so you'll get a 404
  • I want the Build Number and the Build ID
    • This way I can link back to my Azure DevOps site

Adding a Git Commit Hash to your .NET assembly

There's lots of Assembly-level attributes you can add to your .NET assembly. One lovely one is AssemblyInformationalVersion and if you pass in SourceRevisionId on the dotnet build command line, it shows up in there automatically. Here's an example:

[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+d6b3d432970c9acbc21ecd22c9f5578892385305")]
[assembly: AssemblyProduct("hanselminutes.core")]
[assembly: AssemblyTitle("hanselminutes.core")]
[assembly: AssemblyVersion("1.0.0.0")]

From this command line:

dotnet build --configuration Release /p:SourceRevisionId=d6b3d432970c9acbc21ecd22c9f5578892385305

But where does that hash come from? Well, Azure Dev Ops includes it in an environment variable so you can make a YAML task like this:

- task: DotNetCoreCLI@2
displayName: 'dotnet build $(buildConfiguration)'
inputs:
command: 'build'
arguments: '-r $(rid) --configuration $(buildConfiguration) /p:SourceRevisionId=$(Build.SourceVersion)'

Sweet. That will put in VERSION+HASH, so we'll pull that out of a utility class Damian made like this (full class will be shown later)

public string GitHash
{
get
{
if (string.IsNullOrEmpty(_gitHash))
{
var version = "1.0.0+LOCALBUILD"; // Dummy version for local dev
var appAssembly = typeof(AppVersionInfo).Assembly;
var infoVerAttr = (AssemblyInformationalVersionAttribute)appAssembly
.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute)).FirstOrDefault();

if (infoVerAttr != null && infoVerAttr.InformationalVersion.Length > 6)
{
// Hash is embedded in the version after a '+' symbol, e.g. 1.0.0+a34a913742f8845d3da5309b7b17242222d41a21
version = infoVerAttr.InformationalVersion;
}
_gitHash = version.Substring(version.IndexOf('+') + 1);

}

return _gitHash;
}
}

Displaying it is then trivial given the helper class we'll see in a minute. Note that hardcoded paths for my private repo. No need to make things complex.

deployed from commit <a href="https://github.com/shanselman/hanselminutes-core/commit/@appInfo.GitHash">@appInfo.ShortGitHash</a>

Getting and Displaying Azure DevOps Build Number and Build ID

This one is a little more complex. We could theoretically tunnel this info into an assembly as well but it's just as easy, if not easier to put it into a text file and make sure it's part of the ContentRootPath (meaning it's just in the root of the website's folder).

To be clear, an option: There are ways to put this info in an Attribute but not without messing around with your csproj using some not-well-documented stuff. I like a clean csproj so I like this. Ideally there'd be another thing like SourceRevisionID to carry this metadata.

You'd need to do something like this, and then pull it out with reflection. Meh.

<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition="$(BuildNumber) != ''" >
<_Parameter1>BuildNumber</_Parameter1>
<_Parameter2>$(BuildNumber)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition="$(BuildId) != ''" >
<_Parameter1>BuildId</_Parameter1>
<_Parameter2>$(BuildId)</_Parameter2>
</AssemblyAttribute>
</ItemGroup>

Those $(BuildNumber) and $(BuildId) dealies are build variables. Again, this csproj messing around is not for me.

Instead, a simple text file, coming along for the ride.

- script: 'echo -e "$(Build.BuildNumber)\n$(Build.BuildId)" > .buildinfo.json'
displayName: "Emit build number"
workingDirectory: '$(Build.SourcesDirectory)/hanselminutes.core'
failOnStderr: true

I'm cheating a little as I gave it the .json extension, only because JSON files are copying and brought along as "Content." If it didn't have an extension I would need to copy it manually, again, with my csproj:

<ItemGroup>
<Content Include=".buildinfo">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

So, to be clear, two build variables inside a little text file. Then make a little helper class from Damian. Again, that file is in ContentRootPath and was zipped up and deployed with our web app.

public class AppVersionInfo
{
private static readonly string _buildFileName = ".buildinfo.json";
private string _buildFilePath;
private string _buildNumber;
private string _buildId;
private string _gitHash;
private string _gitShortHash;

public AppVersionInfo(IHostEnvironment hostEnvironment)
{
_buildFilePath = Path.Combine(hostEnvironment.ContentRootPath, _buildFileName);
}

public string BuildNumber
{
get
{
// Build number format should be yyyyMMdd.# (e.g. 20200308.1)
if (string.IsNullOrEmpty(_buildNumber))
{
if (File.Exists(_buildFilePath))
{
var fileContents = File.ReadLines(_buildFilePath).ToList();

// First line is build number, second is build id
if (fileContents.Count > 0)
{
_buildNumber = fileContents[0];
}
if (fileContents.Count > 1)
{
_buildId = fileContents[1];
}
}

if (string.IsNullOrEmpty(_buildNumber))
{
_buildNumber = DateTime.UtcNow.ToString("yyyyMMdd") + ".0";
}

if (string.IsNullOrEmpty(_buildId))
{
_buildId = "123456";
}
}

return _buildNumber;
}
}

public string BuildId
{
get
{
if (string.IsNullOrEmpty(_buildId))
{
var _ = BuildNumber;
}

return _buildId;
}
}

public string GitHash
{
get
{
if (string.IsNullOrEmpty(_gitHash))
{
var version = "1.0.0+LOCALBUILD"; // Dummy version for local dev
var appAssembly = typeof(AppVersionInfo).Assembly;
var infoVerAttr = (AssemblyInformationalVersionAttribute)appAssembly
.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute)).FirstOrDefault();

if (infoVerAttr != null && infoVerAttr.InformationalVersion.Length > 6)
{
// Hash is embedded in the version after a '+' symbol, e.g. 1.0.0+a34a913742f8845d3da5309b7b17242222d41a21
version = infoVerAttr.InformationalVersion;
}
_gitHash = version.Substring(version.IndexOf('+') + 1);

}

return _gitHash;
}
}

public string ShortGitHash
{
get
{
if (string.IsNullOrEmpty(_gitShortHash))
{
_gitShortHash = GitHash.Substring(GitHash.Length - 6, 6);
}
return _gitShortHash;
}
}
}

How do we access this class? Simple! It's a Singleton added in one line in Startup.cs's ConfigureServices():

services.AddSingleton<AppVersionInfo>();

Then injected in one line in our _layout.cshtml!

@inject AppVersionInfo appInfo

Then I can use it and it's easy. I could put an environment tag around it to make it only show up in staging:

<environment include="Staging">
<cache expires-after="@TimeSpan.FromDays(30)">
<div class="copyright">&copy; Copyright @DateTime.Now.Year, <a href="https://www.hanselman.com">Scott Hanselman</a>. Design by <a href="http://www.8164.org/">@@jzy</a>, Powered by @System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription and deployed from commit <a href="https://github.com/shanselman/hanselminutes-core/commit/@appInfo.GitHash">@appInfo.ShortGitHash</a> via build <a href="https://dev.azure.com/hanselman/Hanselminutes%20Website/_build/results?buildId=@appInfo.BuildId&view=results">@appInfo.BuildNumber</a> </div>
</cache>
</environment>

I could also wrap it all in a cache tag like this. Worst case for a few days/weeks at the start of a new year the Year is off.

<cache expires-after="@TimeSpan.FromDays(30)">

<cache>

Thoughts on this technique?


Sponsor: This week's sponsor is...me! This blog and my podcast has been a labor of love for over 18 years. Your sponsorship pays my hosting bills for both AND allows me to buy gadgets to review AND the occasional taco. Join me!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Making a cleaner and more intentional azure-pipelines.yml for an ASP.NET Core Web App

March 4, '20 Comments [10] Posted in Azure | DotNetCore
Sponsored By

Azure Pipelines releasing to LinuxA few months back I moved my CI/CD (Continuous Integration/Continuous Development) to Azure DevOps for free. You get 1800 build minutes a month FREE and I'm not even close to using it with three occasionally-updated sites building on it.

It wasn't too hard, but as with all build pipelines you'll end up with a bunch of trial and error builds until you really get it dialed in.

I was working/pairing with Damian today because I wanted to get my git commit hashes and build ids embedded into the actual website so I could see exactly what commit is in production. How to do that will be the next post!

However, while tidying up we noticed some possible speed up and potential issues with my original azurepipeslines.yml file, so here's my new one!

NOTE: There's MANY ways to write one of these. For example, note that I'm allowing the "dotnet restore" to happen automatically as a sign effect of the call to dotnet build. Damian prefers to make that more explicit as its own task so he can see timing info for it. It's up to you, just know the side effects and measure!

Let's read the YAML and see what's up here.

  • My primary Git branch is called "main" so my Pipeline triggers on commits to main.
  • I'm using a VM from the pool that's the latest Ubuntu.
  • I'm doing a Release (not Debug) build and putting that value in a variable that I can use later in the pipeline.
  • I'm using a "runtime id" of linux-x64 and I'm storing that value also for use later. That's the .NET Core runtime I'm interested in.
  • I'm passing in the -r $(rid) to be absolutely clear about my intent at every step.
  • I want to build ONCE so I'm using --no-build on the publish command. It's likely not needed, but because I was using a rid on the build and then not using it later, my publish was wasting time by building again.
  • The dotnet test command uses -r for results (dumb) so I have to pass in --runtime if I want to pass in a rid. Again, likely not needed, but it's explicit.
  • I publish and name the artifact (fancy word for the resulting ZIP file) so it can be used later in the Deployment pipeline.

Here's the YAML

# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core

trigger:
- main

pool:
vmImage: 'ubuntu-latest'

variables:
buildConfiguration: 'Release'
rid: 'linux-x64'

steps:
- task: UseDotNet@2
inputs:
version: '3.1.x'
packageType: sdk

- task: DotNetCoreCLI@2
displayName: 'dotnet build $(buildConfiguration)'
inputs:
command: 'build'
arguments: '-r $(rid) --configuration $(buildConfiguration) /p:SourceRevisionId=$(Build.SourceVersion)'

- task: DotNetCoreCLI@2
displayName: "Test"
inputs:
command: test
projects: '**/*tests/*.csproj'
arguments: '--runtime $(rid) --configuration $(buildConfiguration)'

- task: DotNetCoreCLI@2
displayName: "Publish"
inputs:
command: 'publish'
publishWebProjects: true
arguments: '-r $(rid) --no-build --configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: true

- task: PublishBuildArtifacts@1
displayName: "Upload Artifacts"
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: 'hanselminutes'

Did I miss anything? What are your best tips for a clean YAML file that you can use to build and deploy a .NET Web app?


Sponsor: This week's sponsor is...me! This blog and my podcast has been a labor of love for over 18 years. Your sponsorship pays my hosting bills for both AND allows me to buy gadgets to review AND the occasional taco. Join me!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Love in a time of Corona Virus - Tips, Tricks and Best Practices for Working Remotely

February 28, '20 Comments [21] Posted in Remote Work
Sponsored By

Remote worker ScottIn this time of the Corona Virus and COVID-19, Microsoft has taken the unprecedented move of shutting down their Seattle campus. No one goes in to work until March 25th. That means they're all remote workers! Like me! For 13 years!

Do note that I am deeply sympathetic for the situation we all find ourselves in and I'm deeply aware of the privilege we have as tech/desk workers to be able to do our jobs remotely. I am also (dare I say) looking forward to what I believe will be a dramatic increase in Remote Worker Empathy on the part of the in office folks.

Check out my previous posts on being remote and explore the Remote Work category if you like.

I also want to showcase some of the great tips and suggestion for remote work that are being shared. Here's tips from our own Adrian Murphy who has shared them internally at the company and has given me permission to share them externally. You can follow Adrian on Twitter and thank him his team for sharing all this information and for their kindness!

These tips are written in the context of using Microsoft Teams (which has an extraordinary free plan, BTW) but you can replace those references with Zoom/Skype/Whatever and the tips are the same.

Tips, Tricks and Best Practices for Working Remotely

Working remote can be quite a shift from being in the office, and sometimes the things you take for granted in person suddenly become massive inconveniences when working remotely. Whether this is your first time being remote or you're a remote superstar from the Antarctica office, this collection of tips and best practices may help you get settled.

Communication

  • Be mindful of time zones. Your middle of the day may be someone’s 3:00 am, so when scheduling calls or pinging via Teams, don’t forget to take into account the time zone differences for all involved.
  • Turn on your video when on a call with your team (there’s a bunch of communication which is non-verbal). Occasionally show off your pets on said video. It’s the little things that make it all feel more human.
    • Some might have reduced bandwidth. Consider turning on video at the beginning during intro and turning it off during important parts if you experience hiccups.
  • During calls, make sure to wait a few extra seconds when asking if folks online have any comments. It can take time for folks to un-mute themselves, and sometimes things can chug or hiccup. It’s not as instant as it is face to face, so you don’t want to move on before folks can even get a word out on a call.
    • Some headsets have a hardware mute button and different conference software has hotkeys for mute/unmute. That can make it faster to unmute yourself.
  • If you are in a room full of people with a conference mic, speak loudly and clearly regardless of where you are sitting. While folks in the room may be able to hear you fine from 6-10 feet away, the conference room mic registers barely a whisper for those dialing in.
  • Mute your mic in big meetings, in small meetings it’s up to you—an unmuted mic will also give some of the incidental non-verbal (but vocal) indicators of what you’re thinking/feeling.
  • If you plan to present, prepare for meetings ahead of time by pre-loading web pages, powerpoints, or other presentation materials. During larger calls Teams can sometimes get bogged down, esp with many video sources at once, and this will cause the rest of your software to run slower. Having a room of 15+ people watch a web page load for 30 seconds is a great way to lose friends.
  • Summarize action items or conclusions in the call for everyone when one has been reached. Finalizing a conversation by summarizing the key take away is a great way to make sure the information is correct, and allows the folks on the call have a chance to hear it and respond if need be before the meeting moves on.
  • If you are leading/proctoring a meeting, be prepared to play switchboard operator if multiple people speak up at once. In person we use social cues and body language to naturally reach a speaking order, in a remote call someone is going to have to make sure only one person is speaking at a time, but also that everyone gets heard eventually. It can be as simple as “why don’t we have person A speak first, then person B” and let the conversation flow from there.
    • If someone has something to say but is waiting their turn, and the conversation veers in a different direction for a few mins, make sure to check back with the person who was waiting. It’s possible their comment was addressed during the course of that conversation, but don’t assume so. Offer them the chance to speak before the meeting moves on.
  • If you are presenting from your machine remotely, ZOOM IN. What looks fine to you on a home monitor may be microscopic on a projected screen in a room of 20 people. You can use the free ZoomIt tool from SysInternals and draw on the screen if you like.
  • If possible/acceptable, record meetings in Teams. This allows folks who weren’t able to attend, or may have otherwise missed a moment in the meeting due to a bandwidth hiccup, to catch up after the fact. Add the video link to your meeting notes when sending out (Teams will usually finish processing a recorded video within 10-15 mins of capturing it, and sends a notification to you in outlook that it’s ready).
  • Keep on top of your mails and Teams inboxes. If people can’t walk up to tap you on the shoulder, this is your only interface with your colleagues. Consider enabling pop up notifications or flashing task bar indicators for when you have unread messages.
    • Likewise, be aware that Teams chat is async by nature, just like e-mail. Expect more delay than when you walk to someone’s desk to ask them a question.
  • Never forget that there’s a real person behind every email address, Teams message, and DevOps Tickets.
  • You may have to “read the room” more as a remote worker. You may not be able to see someone’s body language to know whether or not they are joking, smiling, or upset about something.
  • It’s okay to take calls in casual clothes if you're comfortable and able. Don’t wear anything that’s offensive, explicit, etc - use your best judgment - but don’t feel like you have to get super dressed and be uncomfortable all day. Enjoy being home and wear your sweatpants!
  • It’s okay to eat on calls if it’s your dinner or lunch time, we do it all the time in the Boston office! Just mute your mic, chewing sounds are gross!!!!

Equipment

  • Make sure that you machine is properly enrolled in Intune and has VPN access before attempting to work from home
  • Make sure you have at least one of your comm tools (email, teams, slack) on your phone in addition to your laptop, so that you can let people know whats up if you lose power/internet at your home.
  • Keep your laptop batteries charged.
  • Get mobile with unlimited data if possible.
  • Stock up on necessary dongles, adapters, cords, and wires. Lack of necessary connectors can decrease your productivity.
  • Consider a headset for calls, specially ones with an easy to access mute button and fold down mic. It really can improve the quality of your audio. Webcams, specially those not at keyboard level (pointing up your nose) are also important.

Lifestyle

  • Take a little time to make your work environment pleasant. If you’re working in a mop closet with no windows, or the spare-room-equivalent thereof, you probably won’t be that productive. We’re complex beings. Put a plant on your desk, get some music going, clean.
  • You may wish to have a conversation with your kids that goes something like this: “Now, when I’m at my desk this week, it means I’m working. I can be interrupted a little bit sometimes, but most of the time I’ll need to be able to concentrate.”
    • You may wish to choose a work place in your house with a door to make this more explicit for children. “When this door is shut I am at work and you need to find your other parent”
  • EAT. You’ll forget to eat when you’re not surrounded by huge cafeterias or a team of people asking you what you want to do for lunch.
  • Working remotely can be lonely sometimes. Find a way to reproduce the feeling of that office chatter, or the casual hallway/water-cooler conversation. Music, podcast, background TV noise (when appropriate), etc.
  • Get some fresh air from time to time. Normally when you’re at the office you’re walking to/from the office and from your building to the cafeteria for coffee or lunch so you get outside quite a bit, but when working from home and the kitchen is just downstairs, you can very easily go a few days without actually leaving the house. It doesn’t need to be far, and with COVID19 around it probably shouldn’t be, but maybe sit on your porch while you drink your coffee, it helps clear the lungs and the head.
  • Good light is important. Having good lighting closer to natural light wavelengths makes the environment much more pleasant.
  • Build a routine of specific cues to help you switch into “work mode”. For example, you may still get dressed as you would if you were heading into the office to put you into the right mindset for the day.
  • Get interesting drinks and snacks!!! If you drink 12 cans of seltzer a day, make sure you have seltzer in your fridge. If you like getting peanut M&Ms from the team room at 2pm, grab some for your home! You’d be surprised how boring your regular drinks are when you’re used to fridges filled with flavorful things and a huge tea selection etc.

Things to avoid

  • If you are in a meeting room with other folks, try to keep side conversations and noises to a minimum. Imagine you are sitting in the center of the table and hear the things closes to the mic the loudest, because that’s how it is for anyone calling in.
  • Don’t sit right next to the microphone if you are going to be taking notes on a loud keyboard. It will drown out every voice in the room.
  • Don’t disclose sensitive information if there are others in your home office when you’re in a meeting. Generally speaking, it’s better to be using headphones if others are around.
  • Don’t direct a question to more than one individual at a time. This can be mildly awkward in person, but over a call it becomes nearly impossible to figure out who will speak first without any of the visual/bodily cues we normally rely on to resolve speaking order conflicts.
  • Don’t use whiteboards unless they are clearly visible to the room camera (and even then it can be tricky to see). Digital mediums present in Teams are always going to be superior for folks who are calling in.

What are your tips?


Sponsor: This week's sponsor is...me! This blog and my podcast has been a labor of love for over 18 years. Your sponsorship pays my hosting bills for both AND allows me to buy gadgets to review AND the occasional taco. Join me!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

How to set up a tab profile in Windows Terminal to automatically SSH into a Linux box

February 25, '20 Comments [7] Posted in Linux | Win10
Sponsored By

A lovely list of Profiles in Windows TerminalBy now I hope you've installed Windows Terminal. If not, go do that, I'll wait. It's time.

You may also have customize your settings. If you tried terminal a few versions ago and haven't gone back in, it's also time to let the Windows Terminal generate you a nice fresh new profiles.json (settings file). It's OK to zero-out/delete yours. Windows Terminal will regenerate it when it next starts.

I have a number of things in my Terminal dropdown. It looks like this.

However, I'd like to be able to have a profile that ssh's into Linux machines that I use regularly. Perhaps those remote machine can have their own cool menu item? Let's see what that would look like and how we'd do it.

Adding a New Profile to Windows Terminal

Click the down arrow in the Windows Terminal top tab bar. Note that there are a ton of great and useful settings so explore the Settings Schema, and when you're editing the settings make sure that Visual Studio Code is set as your default handler for .json files. That's important because the Windows Terminal settings profile.json includes a JSON Schema and you'll want your settings to have autocomplete/intellisense. This will make it easier to create and discover new settings.

I'll add a profile to the "profiles" array. To start, and to learn, let's add the simplest possible profile! I'm just adding the { } as an array item in the larger profiles [] and giving it a name.

"profiles": [
{
"name": "This is a name"
},

This will make a new menu item in Windows Terminal with the same name. It will have no icon and it'll launch cmd.exe as the default shell because I didn't set any other command line! It I add it at the top (as the first) item in the profiles array it'll also appear first in the menu and have the hotkey Ctrl+Shift+1.

This is lame, so let's add more. I'll add a tabTitle and a commandline.

    {
"name": "This is a name",
"tabTitle": "This is a tab title",
"commandline": "powershell"
},

This menu item will appear as "This is a name" in the menu, but the the tab will be called "This is a tab title." It'll launch powershell. Note that I didn't include .exe even though I could have. I wanted to make sure you're clear that Windows Terminal is basically just called Process.Start so you can set a profile tab to call anything in the PATH, or you can be explicit. I could also add "startingDirectory" and a bunch of other options.

Since I can call anything in the PATH, what else can I get away with?

Using OpenSSH on Windows

You may not have heard but OpenSSH has shipped in Windows for a few years now. That means that a lot of the utilities that you might have installed Putty for are already available in Windows. You can open an admin PowerShell and run one command to ensure OpenSSH's client apps are there:

Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0

This installs the the client, but there's an optional server as well if you'd like.

I'm going to focus only on the client. Skip to the next area if you want to do your SSH'ing from Linux, not Windows.

Here's what's installed in c:\windows\System32\OpenSSH

OpenSSH utilties

Here we've got sftp, scp, and most importantly, ssh.exe and ssh-agent. Since ssh is in the PATH when it's installed with Windows I can change my Windows Terminal profile to look like this and log into my Raspberry Pi 4.

{
"name": "ssh hanselPi4",
"tabTitle": "HanselPi4",
"commandline": "ssh pi@hanselpi4"
},

Note in this screenshot I've got the ssh connection listed at the top, and when I click on it it opens ssh.exe and prompts me for a password. I have no ssh keys on my system that would enable auto-login, hence the password is needed.

ssh'ing into a Raspberry Pi

Automatically SSH'ing/logging into a Linux machine from a Windows Terminal profile

Now this is important, so pay attention. If you have WSL or WSL already on your machine you can certainly just use the SSH keys and utils that are included in your preferred Linux distro.

In that case, your command line in your Windows Terminal profile would be something like:

wsl ssh pi@hanselpi4

or if you want a specific distro, you can launch a distro and ssh from there.

wsl -d Ubuntu-18.04 ssh pi@hanselpi4

If you know Linux, then you're familiar with how to set up your public keys to allow this. However, most folks think you need Putty or some 3rd party tool to do this on Windows so I'll focus on how do to that here.

I want to be able to type "ssh pi@hanselpi4" from my Windows machine and automatically be logged in. More specifically I want to click the profile and have it Just Work.

I will

  • Make a key on my Window machine. The FROM machine, in this case, Windows. Then I want to ssh FROM here TO the remote Linux machine.
  • Tell the Linux machine (by transferring it over) about the public piece of my key and add it to a specific user's allowed_keys.

I'll run ssh-keygen to make a key from my command line on Windows. I just hit enter to generate it but you can make your own filename if you want, just use the full path and make sure you keep track of where things are. Defaults are usually best.

>ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (C:\Users\scott/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in hanselpi4.
Your public key has been saved in hanselpi4.pub.

Remember the path is c:\users\yourname because that's the Windows equivalent of the ~ home folder and the keys are in c:\users\yourname\.ssh.

Now I want to transfer what's in id_rsa.pub over to my Raspberry Pi. You can scp (secure copy) if you want, but it's best to append the key to the authorized_keys file on the destination machine.

NOTE: I'm type'ing (cat on Linux is type on Windows) that text file out and piping it into SSH where I login that remote machine with the user pi and I then cat (on the Linux side now) and append >> that text to the .ssh/authorized_keys folder. The ~ folder is implied but could be added if you like.

Run this command once on Windows to output your key and pipe it over to, and append to, the right file on your remote Linux machine. You'll be prompted for your password once.

type c:\users\scott\.ssh\id_rsa.pub | ssh pi@hanselpi4 'cat >> .ssh/authorized_keys'

Make sure you understand what's happening in the line above.

Adding a profile Icon - the raspberry on top

At this point I can click the menu item in Windows Terminal and automatically be ssh'ed/logged into the remote terminal. But, scandalously, the Terminal menu item has no icon. This is clearly unacceptable M$sft sucks, right? I'll go get a nice 32x32 Raspberry Pi Icon and put it somewhere. You might put yours in a Dropbox or OneDrive so they are available everywhere you go.

Now my profile looks like this:

"profiles": [
{
"name": "ssh hanselPi4",
"tabTitle": "HanselPi4",
"commandline": "ssh pi@hanselpi4",
"icon": "c:/users/scott/downloads/icons8-raspberry-pi-32.png"
},

How lovely is this?

A nice Raspbery Pi icon in my profile

Looks good, has a nice title and icon, and I can use a hotkey to automatically SSH into my remote machine.

One final note, you've already got the Azure Cloud Shell in the Windows Terminal (you can get there for free at http://shell.azure.com in your browser and access a free Linux container anywhere anytime with a persistent cloud drive) but now you can follow the instructions in this post and set up one-click SSH to anywhere.

Hope this is useful!


Sponsor: This week's sponsor is...me! This blog and my podcast has been a labor of love for over 18 years. Your sponsorship pays my hosting bills for both AND allows me to buy gadgets to review AND the occasional taco. Join me!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.