Windows Terminal + PowerShell 的配置

Boxstarter-01

Posted by Miangu on January 9, 2020

2019年,微软发布的设备在我看来,并没有什么让人心动的,倒是 Windows Terminal (WT),以其华丽的界面引起了不小的轰动。实际体验下来,虽然WT还有待进一步改进,比如颜色显示、快捷键之类的问题,但是用来代替朴素的自带cmd或者PowerShell窗口还是绰绰有余的。WT的配置目前还是基于纯文本修改的,对新上手的人来说或许还有些摸不着头脑,在本文中,我们将共同安装并简单配置WT。本文也算是系列Boxstarter的第一篇文章,以后迁移到新电脑时可以快速配置环境,省时省力。不过也因此,文中会出现许多比GUI操作更为复杂的命令行操作,如果不需要纯命令行部署,可以跳过命令行部分。

这个系列我也是一边写一边学习PowerShell一边整理成代码,所以暂时还没有能够发布的一键脚本这种东西,但是当系列基本成型后肯定还是会有的。

前提条件

  • Windows 10 版本号 $\geq$ 1903

安装必要工具

进入开发者模式

可以通过在“设置—更新与安全—开发者”中选择“开发者模式开启”:

Picture2

也可以通过这个PowerShell脚本开启,复制内容,黏贴进一个文本文件并修改扩展名为ps1双击运行即可。或者可以尝试以下代码:

Invoke-WebRequest `
	-Uri "https://raw.githubusercontent.com/ZhangTianrong/TextDepository/master/POSH/psDevMode.ps1" ` 
	-OutFile ("$env:tmp"+"/psDevMode.ps1")
.("$env:tmp"+"/psDevMode.ps1")

这段代码来自这个stack overflow回答。提权方法是新建PowerShell进程,因此会弹出新的窗口。我希望尽量能够改成在原窗口中直接执行。

手动刷新环境变量

我们经常会需要使用一些自制的小脚本或者程序来辅助日常使用,因此不妨建立一个存储这些小工具的文件夹并添加入PATH,方便日后管理和运行。比如Win+R 键入“PowerShell”回车,输入以下代码

New-Item $env:USERPROFILE\Documents\bin -itemtype directory | Out-Null

Set-ItemProperty `

	-Path "HKCU:\Environment\" `
	
	-Name "Path" `
	
	-Value ((Get-ItemPropertyValue -Path "HKCU:\Environment\" -Name "Path") +`
	
		";" + ($env:USERPROFILE) + "\Documents\bin;")

在当前用户的文档文件夹下建立一个bin文件夹,并将此文件夹添加入用户PATH。

我们在使用终端安装程序时可能会修改PATH,但是PATH的变化一般只会在终端重启后才应用,为了避免反复重启终端,我们使用Chocolatey提供的RefreshEnv.cmd来实现手动刷新环境变量的效果。

Invoke-WebRequest -Uri "https://raw.githubusercontent.com/chocolatey/choco/master/src/chocolatey.resources/redirects/RefreshEnv.cmd" -OutFile ("$env:USERPROFILE"+"\Documents\bin\RefreshEnv.cmd")

这样以后在PowerShell终端中输入refreshenv即可。在以下步骤中,如果出现command not found时均应该可使用refreshenv解决。

如使用图形界面操作可能并不需要频繁刷新环境变量,但是后文中我们还会添加不少脚本到PATH中,可以新建文件夹后揿Win并搜索“environment”;点击“编辑系统环境变量”,选中用户环境变量中的PATH并点击“编辑”;新建一条,填入方才新建文件夹的完整路径。

example

Scoop: Windows下的包管理器

其实安装scoop存在着作者自己的私货,本文中WT的配置并不依赖于scoop,但是使用scoop可以轻松安装并整理安装的程序,最主要的是用作教程可以避免许多不必要的可能会出现问题的安装细节。

  1. 使用管理员身份打开PowerShell:

    1. Win+R 打开“运行”窗口
    2. 键入“PowerShell”并Ctrl+Shift+Enter
    3. 在UAC窗口中允许运行
  2. 安装scoop,详见官方指南

    # Change execution policy
    Set-ExecutionPolicy RemoteSigned -scope CurrentUser
    # Download and install scoop
    iwr -useb get.scoop.sh | iex
    # Add useful buckets (resembles repos in apt)
    scoop bucket add extras
    scoop bucket add versions
    

Sudo for Windows

众所周知sudo是在Unix-like系统中用来以superuser权限运行命令的;而在Windows中,我们经常只能通过像在上个小节中那样直接以管理员权限打开整个终端,sudo for Windows可以帮助我们在常规终端中自动弹出UAC窗口,提权运行代码。

在PowerShell中运行scoop install sudo 即可完成安装,使用方法和Linux中类似,即sudo+命令运行。

安装 Windows Terminal

Edit: 现在微软自己也提供了一个命令行包管理器winget-cli,相应的安装命令是winget install --id Microsoft.WindowsTerminal --exact算是比较好地解决了纯命令部署UWP软件的问题,但是因为目前winget还在preview阶段,建议安装渠道是加入insider计划然后使用start ms-windows-store://pdp/?productid=9nblggh4nns1仍在商店中安装,而自行build则更加麻烦了,所以暂时还是没有办法提供简单的纯命令部署,方法3依旧是个人的推荐。

可以在Microsoft Store中安装Windows Terminal (Preview),暂时没有找到稳定的可以通过命令行安装Microsoft Store程序的方法。暂定三套方案

  1. 通过PowerShell命令start ms-windows-store://pdp?productId=9N0DX20HK701打开安装页面并提示用户安装,安装完成后在原来窗口中按回车继续
  2. 通过该网站下载appx文件再使用WinAppDeployCmd.exe部署
  3. 使用chocolatey安装非官方版本的WT:choco install microsoft-windows-terminal

就目前而言在不追求纯命令的情况下肯定首选方法1在商店界面进行安装,而要求纯命令的情况下则还是方法3最简单易行,相应的代价则是只能通过chocolatey获取更新。

Windows Terminal 的配置文件

Edit: 这么长时间了,还是没有把这一段通过type或者Invoke-WebRequest写成命令,实在有点拖拉……近期看看能不能补上吧。

使用你最喜欢的文本编辑器比如VS Code打开WT的配置文件(如果不知道什么是文本编辑器,那可以直接使用记事本,将code替换成notepad即可,使用ii则是通过默认程序打开的一般操作)

code `
	("$env:USERPROFILE\AppData\Local\Packages\" + `
	(Get-ChildItem `
		$env:USERPROFILE\AppData\Local\Packages\Microsoft.WindowsTerminal* `
		-Name) + `
	"\LocalState\profiles.json")

可见这是一个JSON数据体,官方介绍在Github上可见,我们接下来挑选介绍一下其中关键项的含义:

  • defaultProfile: WT可以开启多个选项卡,每个选项卡可以运行不同终端,通过修改该项可以修改初始页和默认新建标签页中运行的终端。其值是指定终端的GUID,默认是PowerShell

  • scheme: 是一个字典列表,定义了不同的颜色方案

    • name: 改颜色方案的名称
    • backgroundforeground, selectionBackground: hex或者颜色名称,设置背景、前景、选中部分背景的颜色,含义和传统cmd属性中相同,可以参考着来看。
    • black, blue, etc.: 确定颜色名称对应的颜色
  • profiles: 是一个字典列表,每一项代表一种终端配置

    接下来我们以PowerShell为例解释一下字典中每个键的含义:

    • 基本:
      • guid: 即终端程序的guid,如果要添加新的终端,可以参照该网页的操作找到对应的guid
      • name: 即该终端在WT中的显示名称,出现在终端选择菜单和标签页默认标题中
      • icon: 图标路径,显示在终端选择菜单和标签页标题中
      • commandline: 指在开启标签页是调用的命令,可以添加更多option来控制开启时的状态,例如运行不同的profile.ps1文件等
      • hidden:是否在终端选择菜单中显示该配置,比如默认自带的Azure配置不必删去,设置隐藏即可,以防止日后用到
    • 外观:
      • colorScheme: 选择应用scheme中定义的各种方案
      • acrylicOpacity: $0\sim 1$,控制窗口透明度;若无效请手动添加'useAcrylic':true
      • backgroundforeground, selectionBackground: hex值背景、前景、选中部分背景的颜色,可以覆盖colorScheme中的设置
      • backgroundImage: 背景图片的路径,另有backgroundImageAlignment, backgroundImageOpacity, backgroundImageStretchMode 等选项控制图片的位置、透明度和填充方式
      • fontFace: 显示使用的字体名
      • padding: 四个由逗号分隔的整数,控制文本到和窗口边界的左、上、右、下距离
      • scrollbarState: “visible”或”hidden”,控制是否显示滚动条
  • keybindings: 一个字典,键是以字符串数组形式存储的快捷键组合,值是对应的操作,目前能够使用的操作数很有限,具体详见官方说明。

在此,也给出我目前在使用的配置,在默认文件上仅针对PowerShell进行了修改,以后会加入对WSL的配置。我们需要先行下载所需的字体文件,该字体在糅合Consolas和雅黑之外还添加了Powerline字符,这在接下来oh-my-posh模块的显示中有着至关重要的作用。

{
    "$schema": "https://aka.ms/terminal-profiles-schema",

    "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",

    "profiles":
    [
        {
            // Make changes here to the PowerShell.exe profile
            "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
            "name": "Windows PowerShell",
            "commandline": "PowerShell.exe",
            "hidden": false,
            "colorScheme": "Solarized Dark",
            "useAcrylic" : true,
            "background" : "#29292e",
            "backgroundImageOpacity" : 0.95,
            "acrylicOpacity" : 0.8,
            "fontSize" : 11,
            "fontFace" : "Consolas-with-Yahei"
        },
        {
            // Make changes here to the cmd.exe profile
            "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
            "name": "cmd",
            "commandline": "cmd",
            "hidden": false
        },
        {
            // Make changes here to the Azure profile
            "guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b8}",
            "hidden": true,
            "name": "Azure Cloud Shell",
            "source": "Windows.Terminal.Azure"
        }
    ],

    // Add custom color schemes to this array
    "schemes": [],

    // Add any keybinding overrides to this array.
    // To unbind a default keybinding, set the command to "unbound"
    "keybindings": []
}

效果如下:

image-20200106202532565

注意,即使使用了我的配置你现在也不会有这样的效果,因为例如PowerShell等许多终端有自己的高亮显示设定,因此高亮部分(如变量、字符串、选项、错误等)的颜色不受WT设置控制。

PowerShell 高亮色彩的控制

PowerShell中的颜色显示由Set-PSReadLineOption -Colors控制,我们通过sudo notepad $pshome\profile.ps1添加相关语句。如果想要达到和我相同的颜色效果,可以使用以下代码:

((Get-Content -path $pshome\profile.ps1) + `
"`n" + "Set-PSReadLineOption -Colors @{" + `
"  Type = `"#d6cbb7`";" + `
"  Parameter = `"#b58900`";" + `
"  Command = `"#fdf6e3`";" + `
"  Keyword = `"#738309`";" + `
"  Variable = `"#70bc98`";" + `
"  Comment = `"#0d1321`";" + `
"  Emphasis = `"#645822`";" + `
"  Number = `"#f9e8d0`";" + `
"  Operator = `"#b5968a`"}")|Out-File -FilePath $env:temp\profile.tmp
sudo cp $env:temp\profile.tmp $pshome\profile.ps1 -Force
.$pshome\profile.ps1

值得注意的是,应该尽量使用HEX码,因为颜色名称对应的颜色可能会受到WT配置中colorScheme定义的影响。想要观察实际使用情况,可以使用通过.($pshome+'\profile.ps1')重新载入配置文件,再通过PSReadLineOption命令查看,效果如下

image-20200107203718003

如果在重新载入配置时出现错误,不要慌张,可能只是你的配置中有一些诸如常值变量定义等操作而已。

其他内置程序的颜色输出

PowerShell内置了许多cmdlet,要进行颜色输出只能一个个搞定。这里以基于Get-Childls/dir作为例子,给出解决方案。参考这一篇博客进行修改,防止了幽灵空行之后的代码Color-Ls可以在这里下载。本质上先获取原本输出,再根据是否是文件夹、扩展名等信息调整Write-Host的颜色。

其中用到了Windows PowerShell Cookbook一书中给出的New-CommandWrapper方法,代码可以在这里下载。注意,我们需要将New-CommandWrapper.ps1添加入PATH才能在Color-Ls.ps1中直接使用。否则需要在Color-Ls.ps1中添加修改session PATH的语句。Color-Ls.ps1中将ls/dir两个别名重新定义过,因此只要将这段代码导入profile,就可以在任何PowerShell终端里获得彩色输出了。效果如下

image-20200107203132434

Oh my Posh

相信很多人在Linux上都用过zshoh-my-zsh,其丰富的主题和强大的定制性为人称道。oh-my-posh是一个将类似主题体验带到PowerShell的模块。安装方法很简单,

Install-Module posh-git -Scope CurrentUser
Install-Module oh-my-posh -Scope CurrentUser
refreshenv
Import-Module oh-my-posh
Set-Theme -name Agnoster

Agnoster之外,自带主题还有很多,可以在这里https://github.com/JanDeDobbeleer/oh-my-posh#themes找到列表。在选定了喜欢的主题后就可以将相关代码导入profile了,如

Import-Module oh-my-posh
Set-Theme -name Agnoster

设置完成后的效果如下

image-20200112172053533

右键快捷菜单

在使用原生PowerShell或者cmd时可以方便地通过右键菜单在当前目录打开终端,我们希望WT也具有这样的功能。关于这项功能的实现方法,在这个issue里有很长的讨论,修修补补了很多次而且众说纷纭,我在此总结以下我使用的方法。

注意,其中第三行里我将新创建的$env:USERPROFILE\AppData\Local\terminal\文件夹添加进了Windows Defender的白名单,这是因为为了避免批处理文件运行时闪现黑框,我将其打包进了一个可执行文件,但是解压出批处理文件并运行这一操作被很多杀毒软件认为时危险的。我会给出批处理文件原来的内容,有疑虑的读者可以下载BAT2EXE自行打包,使用打包过的程序只是为了方便自动运行。

$_path = "$env:USERPROFILE"+"\AppData\Local\terminal\"
$_profile = "$env:USERPROFILE" + `
	"\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
mkdir -p $_path
sudo Add-MpPreference -ExclusionPath "$_path"
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/ZhangTianrong/TextDepository/master/WinTer/WTRightClick.zip" -OutFile ("$_path"+"WTRightClick.zip")
Expand-Archive -Path ("$_path"+"WTRightClick.zip") -DestinationPath "$_path"
((Get-Content -path ($_path + 'raw.reg') -Raw) -replace "<PLACEHOLDER>",$_path.Replace('\', '\\')) | Set-Content -Path ($_path + 'raw.reg')
sudo reg import ($_path + 'raw.reg')
del ($_path + 'raw.reg')
mkdir -Force $env:USERPROFILE\Documents\WindowsPowerShell
echo $null >> $_profile
((Get-Content -path $_profile) + `
"`nif (`$env:wt_PowerShell_dir) {" + `
"  if (Test-Path `$env:wt_PowerShell_dir) {" + `
"    cd `$env:wt_PowerShell_dir" + `
"    Remove-Item Env:\wt_PowerShell_dir" + `
"  }" + `
"}")|Out-File -FilePath $env:temp\profile.tmp
cp $env:temp\profile.tmp $_profile -Force
Remove-Variable -Name _path
Remove-Variable -Name _profile

WTRightClick.zip 中包含三个文件:launchwt.exe, WindowsTerminal.icoraw.reg. 其中launchwt.exe打包自以下批处理文件:

set wt_PowerShell_dir=%cd%
start %LOCALAPPDATA%\\Microsoft\\WindowsApps\\wt.exe
exit

WindowsTerminal.ico是WT的官方图标,raw.reg内容如下

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\wt]
@="Windows terminal here"
"Icon"="<PLACEHOLDER>WIndowsTerminal.ico"

[HKEY_CLASSES_ROOT\Directory\Background\shell\wt\command]
@="<PLACEHOLDER>launchwt.exe"

上述代码中针将<PLACEHOLDER>替换为了真实路径,因为使用%USERPROFILE%似乎无法识别(但是我现在觉得其实替换成$env:USERPROFILE或许就可以了)。而之所以没有用PowerShell直接修改注册表,完全是因为PowerShell用起来太麻烦,既然需要下载图标和可执行程序,就不妨直接一起下载了……

PowerShell 启动过慢的问题

有时PowerShell启动过程中加载模块过多,会导致启动缓慢,长达数秒甚至将近半分钟。目前我们的PowerShell还相对轻量,我曾经遇到的一个拖慢启动速度的情况是源于安装Anaconda后,PowerShell profile中添加的启动并链接conda的代码。

conda在4.6版本之后刚刚添加对PowerShell的支持,目前还在改进中,目前我采取的办法是推迟加载conda模块,只有在需要使用时才进行加载。方法很简单粗暴,可以将profile中的相关部分截取出来放在之前建立的bin文件夹中,命名为例如conda4PS等即可。

不过,在使用VS code时,我们希望终端打开时能根据设置的解释器加载对应环境,因此又必须事先链接conda模块。为此我们可以在VS code的用户设置中添加

"terminal.integrated.shellArgs.windows": ["-noexit", "conda4ps"]

注意,这只在选择PowerShell作为终端时才有效,conda对cmd的支持似乎已经很完善了,如果使用cmd则没有必要再修改。

插曲:PATH 丢失找回

大手大脚的我想当然地用Windows的setx命令修改了用户PATH,然后就是无尽的吐槽……

这个历史遗留的上古dos命令有以下大坑需要牢记:

  • cmd中的%PATH%是系统PATH后面接上用户PATH,因此通过不加\M修改用户PATH会将系统PATH也写入用户PATH (那不加\M怎么用啊……)
  • setx最多处理1024个字符,于是如果你的%PATH%过长,setx会先斩后奏地直接将它截断……

那么如果有人和我一样不慎中枪或者只是单纯没有备份PATH然后PATH出现问题了怎么办呢?天幸,我找到了这个办法来找回原来的PATH,才免去了重新导入半个月前的注册表的下下策:

  1. 下载Windows官方的Process Explorer
  2. 运行并选中一个在PATH出现问题之前就开始运行的进程,比如开机自启的OneDrive.exe
  3. 右键并选中“属性”,在弹出的窗口中切换至“环境”选项卡
  4. 找到变量Path并Ctrl+c复制到任何一个文本编辑器中
  5. 观察这串字符,找到系统与用户PATH的分界,比如当出现你的用户名时,那么肯定已经处于用户PATH中了

Picture1

后续

本来这一篇是想要主要讲WT的配置的,但是写着写着就变成PowerShell配置了。PowerShell相比于cmd还是有很多更加强大的地方的,只是使用起来总有些别扭,所以只好努力适应了。其实之前也做过WSL和WT的配置,但是新的电脑上还没有装WSL,毕竟WSL2仍然没有解决显卡虚拟化的问题,WSL对我来说,目前还没有必要性。但是本着折腾的初心,将来还是很有可能更新相关内容的。如果更新,应该会在本文中继续填补而不是新开一篇,毕竟感觉本文中还有很多坑没有填上,估计会经历一段时间频繁的修订。