PowerShell 技能连载 - 创建图标

在上一个技能中,我们展示了如何微调 “Windows Terminal” 并将新的项目添加到可启动应用程序列表中。如果要为这些条目添加图标,则需要适当的图标文件。

这是一些从可执行文件中提取图标的 PowerShell 代码。您可以在 Windows Terminal 和其他地方使用生成的 ICO 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# create output folder
$destination = "c:\icons"
mkdir $destination -ErrorAction Ignore


Add-Type -AssemblyName System.Drawing

# extract PowerShell ISE icon
$path = "$env:windir\system32\windowspowershell\v1.0\powershell_ise.exe"
$name = "$destination\ise.ico"
[System.Drawing.Icon]::ExtractAssociatedIcon($path).ToBitmap().Save($name)

# extract Visual Studio Code icon
$Path = "$env:LOCALAPPDATA\Programs\Microsoft VS Code\code.exe"
$name = "$destination\vscode.ico"
[System.Drawing.Icon]::ExtractAssociatedIcon($path).ToBitmap().Save($name)

explorer $destination

PowerShell 技能连载 - 调优 Windows Terminal

在前面的技能中,我们介绍了如何通过 Microsoft Store 在 Windows 10 上安装 “Windows Terminal”。Windows Terminal 将 PowerShell 控制台放在单独的标签页中,非常实用。

您可以通过编辑设置文件来控制选项卡下拉列表中可用的控制台类型:在 Windows Terminal 中,在标题栏中单击向下箭头按钮,然后选择“设置”。这将在关联的编辑器中打开一个 JSON 文件。如果没有与 JSON 文件关联的编辑器,则可以选择一个或使用记事本。

“配置文件”部分列出了您可以使用“向下箭头”按钮在 Windows Terminal 中打开的控制台类型。这是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
"profiles":
    {
        "defaults":
        {
            // Put settings here that you want to apply to all profiles.
        },
        "list":
        [
            {
                // Make changes here to the powershell.exe profile.
                "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
                "name": "Windows PowerShell",
                "commandline": "powershell.exe",
                "hidden": false,
                "useAcrylic": true,
        "acrylicOpacity" : 0.8,
            },
            {
                // Make changes here to the cmd.exe profile.
                "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
                "name": "Command Shell",
                "commandline": "cmd.exe",
                "hidden": false,
                "useAcrylic": true,
        "acrylicOpacity" : 0.8,
            },
            {
                "guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
                "hidden": false,
                "name": "PowerShell",
                "source": "Windows.Terminal.PowershellCore",
                "useAcrylic": true,
        "acrylicOpacity" : 0.8,
            },
            {
                "guid": "{2595cd9c-8f05-55ff-a1d4-93f3041ca67f}",
                "hidden": false,
                "name": "PowerShell Preview (msix)",
                "source": "Windows.Terminal.PowershellCore",
                "useAcrylic": true,
        "acrylicOpacity" : 0.8,
            },
            {
                "guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b8}",
                "hidden": false,
                "name": "Azure Cloud Shell",
                "source": "Windows.Terminal.Azure",
                "useAcrylic": true,
        "acrylicOpacity" : 0.8,
            },
            {
                "guid": "{0caa0dae-35be-5f56-a8ff-afceeeaa6101}",
                "name": "Terminal As Admin",
                "commandline": "powershell.exe -command start 'C:/wt/wt.exe' -verb runas",
                "icon": "ms-appx:///Images/Square44x44Logo.targetsize-32.png",
                "hidden": false
            },
            {
                "guid": "{0ca30dae-35be-5f56-a8ff-afceeeaa6101}",
                "name": "ISE Editor",
                "commandline": "powershell.exe -command powershell_ise",
                "icon": "c:/wt/ise.ico",
                "hidden": false
            },
            {
                "guid": "{b39ac7db-ace0-4165-b312-5f2dfbbe4e4d}",
                "name": "VSCode",
                "commandline": "cmd.exe /c \"%LOCALAPPDATA%/Programs/Microsoft VS Code/code.exe\"",
                "icon": "c:/wt/vscode.ico",
                "hidden": false
            }
        ]
    }

如您所见,您可以定义任何可执行文件的路径,使用 “useAcrylic” 和 “acrylicOpacity” 等选项可以控制透明度。

看一下列表末尾的条目:它们说明了如何使用下拉菜单启动外部程序,例如编辑器(VSCode、ISE),甚至使用管理员权限打开 Windows Terminal。

诀窍是使用 “cmd.exe /c“ 或 “powershell“ 启动外部应用程序。如果直接启动外部应用程序,这将导致空白的选项卡窗口保持打开状态,直到再次关闭外部应用程序。

还要注意如何将喜欢的图标添加到列表项:使用 “icon” 并提交位图或 ico 文件。在即将发布的技巧中,我们将展示如何通过 PowerShell 代码创建此类图标文件。

如果您打算将新条目添加到列表中,请记住每个条目都需要一个唯一的 GUID。在 PowerShell 中,可以使用 New-Guid 来创建一个。

保存 JSON 文件后,修改立即生效。如果您进行的 JSON 编辑损坏了文件或引入了语法错误(例如不匹配的引号等),则 Windows Terminal 会报错。最好在编辑之前制作备份副本,并使用像 VSCode 这样的编辑器来支持 JSON 格式,并通过语法错误提示来帮助您。

PowerShell 技能连载 - 将 Windows Terminal 变成便携式应用程序

在 Windows 10 上,任何 PowerShell 用户都可以使用一个很棒的新工具:Windows Terminal。它使您可以同时使用多个 PowerShell 和其他控制台选项卡,并且可以混合使用 Windows PowerShell、PowerShell 7 和 Azure CloudShell 控制台。您可以从 Microsoft Store 安装 Windows Terminal。

与任何应用程序一样,Windows Terminal 由 Windows 管理,可以随时更新。而且它总是“按用户”安装。

如果计划在其中一个控制台中运行冗长的任务或关键业务脚本,则可能需要将该应用程序转换为仅由您自己控制的便携式应用程序。这样,多个用户也可以使用 Windows App。

以下脚本要求您已安装 Windows Terminal 应用程序,并且必须以管理员权限运行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#requires -RunAsAdmin

# location to store portable app
$destination = 'c:\windowsterminal'


# search for installed apps...
Get-ChildItem "$env:programfiles\WindowsApps\" |
# pick Windows Terminal...
Where-Object name -like *windowsterminal* |
# find the executable...
Get-ChildItem -Filter wt.exe |
# identify executable versions...
Select-Object -ExpandProperty VersionInfo |
# sort versions...
Sort-Object -Property ProductVersion -Descending |
# pick the latest...
Select-Object -First 1 -ExpandProperty filename |
# get parent folder...
Split-Path |
# dump folder content...
Get-ChildItem |
# copy to destination folder
Copy-Item -Destination $destination -Force

# open folder
explorer $destination

# run portable app
Start-Process -FilePath "$destination\wt.exe"

PowerShell 技能连载 - 为任何用户启动 Windows 终端

在 Windows 10 上,任何 PowerShell 用户都可以使用一个很棒的新工具:Windows Terminal。它使您可以同时使用多个 PowerShell 和其他控制台选项卡,并且可以混合使用 Windows PowerShell、PowerShell 7 和 Azure Cloud Shell 控制台。您可以从 Microsoft Store 安装Windows Terminal。

由于 Windows Terminal 是一个“应用程序”,因此始终以为每个用户独立安装。只有事先为该用户安装了该应用程序,才可以通过其可执行文件 wt.exe 启动它。

启动 Windows Terminal 的另一种方法是以下命令:

1
start shell:appsFolder\Microsoft.WindowsTerminal_8wekyb3d8bbwe!App

如果此调用不能帮助您从其他用户帐户启动 Windows Terminal,那么在即将发布的技能中,我们将介绍如何将应用程序转变为不再由 Windows 管理的便携式应用程序。取而代之的是,您可以用任何用户(包括高级帐户)运行它。敬请关注。

PowerShell 技能连载 - 彻底删除 AD 对象

许多 Active Directory 对象都受到保护,无法删除。尝试删除它们时,会出现错误,从而防止您意外删除无法还原的用户帐户。

当然,这可以防止您合法删除甚至将对象移动到新的 OU。

若要确定是否防止意外删除 AD 对象,请使用以下命令:

1
Get-ADObject ‹DN of object› -Properties ProtectedFromAccidentalDeletion

要关闭保护,即在您计划移动或删除对象时,请将属性设置为 $false

1
Set-ADObject ‹DN of object› -ProtectedFromAccidentalDeletion $false

PowerShell 技能连载 - 以可点击图标的方式部署 PowerShell(第 2 部分)

在上一个技巧中,我们说明了如何在Windows资源管理器快捷方式文件中嵌入多达 4096 个字符的PowerShell代码并生成可单击的PowerShell代码。

只需右键单击链接文件并打开属性对话框,即可轻松查看嵌入的 PowerShell 代码。

通过非常简单的调整,您就可以隐藏有效的负载代码。运行以下代码以在桌面上生成示例可单击的 PowerShell 图标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$code = {
# place your code here (must be less than 4096 characters)
# (this example generates a battery report on notebooks
# and opens it in your default browser)
$r = "$env:temp\report.html"
powercfg /batteryreport /duration 14 /output $r
Invoke-Item -Path $r
Start-Sleep -Seconds 2
Remove-Item -Path $r
}

# turn code into a one-liner, remove comments, escape double quotes
# NOTE: this is a very simplistic conversion. Does not support block comments
# or quoted double quotes or any edgy stuff
# USE with simple staight-forward code only
$oneliner = $code.ToString().Trim().Replace('"','\"').Replace([Char[]]10,'').
Split([Char[]]13).Trim().Where{!$_.StartsWith('#')} -join ';'

# create path to a link file. It is always placed on your desktop
# and named "clickme.lnk"
$desktop = [Environment]::GetFolderPath('Desktop')
$linkpath = Join-Path -Path $desktop -ChildPath 'ClickMe.lnk'

# create a blank string of 260 chars
$blanker = " " * 260

# create a shortcut file
$com = New-Object -ComObject WScript.Shell
$shortcut = $com.CreateShortcut($linkpath)
# minimize window so PowerShell won't pop up
$shortcut.WindowStyle = 7
# use a different icon. Adjust icon index if you want
$shortcut.IconLocation = 'shell32.dll,8'
# run PowerShell
$shortcut.TargetPath = "powershell.exe"
# submit code as an argument and prepend with a blank string
# so payload is hidden in the properties dialog
$shortcut.Arguments = "$blanker-noprofile $oneliner"

# save and create the shortcut file
$shortcut.Save()

当您双击桌面上的 “clickme” 图标时,嵌入的负载代码将运行并创建电池报告,然后将其显示在默认浏览器中。

右键单击图标并选择“属性”时,将不会显示嵌入式 PowerShell 代码。该对话框仅显示powershell.exe的路径,但不显示任何参数。

这是通过在参数前面加上 260 个空白字符来实现的。快捷方式文件最多支持 4096 个字符的命令行,而 Windows 资源管理器及其对话框仅显示前 260 个字符。要查看嵌入式有效负载,用户必须使用上面的 PowerShell 代码以编程方式读取快捷方式文件并转储 arguments 属性。

PowerShell 技能连载 - 以可点击图标的方式部署 PowerShell(第 1 部分)

您可以使用 .lnk 文件将小型 PowerShell 解决方案部署到最终用户。以下是实现方法:

使用以下代码,然后将 $code 中的有效负载代码替换为您希望点击图标时执行的任意 PowerShell 代码。只要确保代码总数少于 4096 个字符即可。然后运行脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$code = {
# place your code here (must be less than 4096 characters)
# (this example generates a battery report on notebooks
# and opens it in your default browser)
$r = "$env:temp\report.html"
powercfg /batteryreport /duration 14 /output $r
Invoke-Item -Path $r
Start-Sleep -Seconds 2
Remove-Item -Path $r
}

# turn code into a one-liner, remove comments, escape double-quotes
# NOTE: this is a very simplistic conversion. Does not support block comments
# or quoted double quotes or any edgy stuff
# USE with simple staight-forward code only
$oneliner = $code.ToString().Trim().Replace('"','\"').
Replace([Char[]]10,'').Split([Char[]]13).
Trim().Where{!$_.StartsWith('#')} -join ';'

# create path to a link file. It is always placed on your desktop
# and named "clickme.lnk"
$desktop = [Environment]::GetFolderPath('Desktop')
$linkpath = Join-Path -Path $desktop -ChildPath 'ClickMe.lnk'

# create a shortcut file
$com = New-Object -ComObject WScript.Shell
$shortcut = $com.CreateShortcut($linkpath)
# minimize window so PowerShell won't pop up
$shortcut.WindowStyle = 7
# use a different icon. Adjust icon index if you want
$shortcut.IconLocation = 'shell32.dll,8'
# run PowerShell
$shortcut.TargetPath = "powershell.exe"
# submit code as an argument
$shortcut.Arguments = "-noprofile $oneliner"

# save and create the shortcut file
$shortcut.Save()

结果是在桌面上出现一个名为 “clickme” 的图标。当双击该图标时,将运行嵌入的 PowerShell 代码。如果您没有在上面的示例中更改有效负载脚本,它将生成电池报告并将其显示在默认浏览器中。

由于有效载荷代码已嵌入图标文件中,因此您可以方便地将其地传递给其他人或进行部署。

在 $code 中调整嵌入式有效负载脚本时,需要考虑以下几点:

  1. 代码必须少于 4096 个字符
  2. 不要使用块注释,或最好删除所有注释行(以减小有效负载大小)
  3. 不要使用带引号的双引号,因为必须将双引号转义,而脚本不是很智能。它只是转义找到的所有双引号。

PowerShell 技能连载 - 试用新的 SSH 远程操作

如果您想试用 SSH 而非 WinRM 的新 PowerShell 远程操作替代方案,请确保先安装了 PowerShell 7。

接下来,在 PowerShell 7 中,安装以下模块:

1
PS> Install-Module -Name Microsoft.PowerShell.RemotingTools -Scope CurrentUser

一旦安装了模块,就可以在提升权限的 PowerShell 7 Shell 中,仅需一个调用即可启用基于 SSH 的新远程处理:

1
PS> Enable-SSHRemoting

安装完成后,打开提升的 PowerShell 7 Shell,然后尝试远程连接到您自己的计算机上:

1
PS> Enter-PSSession -HostName $env:computername -UserName remotingUser

一旦能按预期运行后,就可以使用相同的技术将跨平台从任何 PowerShell 7 实例远程连接到另一个实例。

PowerShell 技能连载 - 测试应用程序是否存在

这是一段简单的单行代码,可以测试您的系统(或任何其他应用程序)上是否安装了 PowerShell 7:

1
2
3
4
5
6
7
8
9
10
11
12
# name of application you want to test
$name = 'pwsh'

# try and find the application. Discard result and errors.
Get-Command -Name $name -ErrorAction Ignore | Out-Null

# if there was an error, the application does not exist
$exists = $?


# output result
"Does $name exist? $exists"

本质上,代码使用 Get-Command 按名称检查应用程序。 $env:path 中列出的文件夹中所安装的所有应用程序都将被识别出来。

如果将 PowerShell 7 安装在 “$env:path” 中列出的默认文件夹之一中,则上面的代码将返回 $true。如果尚未安装 PowerShell 7 或将其安装在 Get-Command 无法发现的某些专用文件夹中,它将返回 $false

PowerShell 技能连载 - 将文件路径转为 8.3 格式(第 2 部分)

在上一篇文章中,我们解释了如何使用旧的 COM 组件将默认的长路径名转换为短的 8.3 路径名。虽然可以偶尔进行转换,但使用 COM 组件的速度很慢且占用大量资源。

一种“清洁”的方法是直接使用 Windows API 调用。您可以通过以下方法访问将长文件路径转换为短文件路径的内部方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# this is the long path to convert
$path = "C:\Program Files\PowerShell\7\pwsh.exe"


# signature of internal API call
$signature = '[DllImport("kernel32.dll", SetLastError=true)]
public static extern int GetShortPathName(String pathName, StringBuilder shortName, int cbShortName);'
# turn signature into .NET type
$type = Add-Type -MemberDefinition $signature -Namespace Tools -Name Path -UsingNamespace System.Text

# create empty string builder with 300-character capacity
$sb = [System.Text.StringBuilder]::new(300)
# ask Windows to convert long path to short path with a max of 300 characters
$rv = [Tools.Path]::GetShortPathName($path, $sb, 300)

# output result
if ($rv -ne 0)
{
$shortPath = $sb.ToString()
}
else
{
$shortPath = $null
Write-Warning "Shoot. Could not convert $path"
}


"Short path: $shortPath"