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"

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

许多年前,文件和文件夹名称最多包含 8 个字符,而这些短路径名称仍然存在。它们甚至仍然有用:短路径名永远不会包含空格和其他特殊字符,因此永远不需要引号或转义。当路径变得很长时,短路径也可能会有所帮助。

但是,如何获得默认长路径名称的短路径名称呢?一种方法是使用 Windows 脚本宿主使用的旧 COM 组件:

1
2
3
4
5
6
7
8
9
10
11
# take any path
# in this example, I am taking the path where Powershell 7 is installed
# this requires that PowerShell 7 in fact is installed.
# You can use any other path as well
$path = (Get-Command -Name pwsh).Source

"Long path: $path"

# convert it to 8.3 short name
$shortPath = (New-Object -ComObject Scripting.FileSystemObject).GetFile($path).ShortPath
"Short path: $shortPath"

结果看起来像这样:

Long path: C:\Program Files\PowerShell\7\pwsh.exe
Short path: C:\PROGRA~1\POWERS~1\7\pwsh.exe

PowerShell 技能连载 - 识别 PowerShell 宿主和路径

这是一个快速的单行代码,用于标识当前 PowerShell 宿主的完整路径:

1
2
PS> (Get-Process -Id $pid).Path
C:\Program Files\PowerShell\7\pwsh.exe

该路径会告诉您当前宿主的位置,并且您可以检查代码是否在 Windows PowerShell、PowerShell 7 或PowerShell ISE 中执行。

用类似的方法,您还可以按名称查找可执行文件的路径。例如,如果您想知道 PowerShell 7 在系统上的安装位置,请尝试以下操作:

1
2
PS C:\> (Get-Command -Name pwsh).Source
C:\Program Files\PowerShell\7\pwsh.exe

当然,如果找不到可执行文件,此行将产生错误。它必须位于 $env:path 中列出的文件夹之一中。

PowerShell 技能连载 - 使用在线帮助(第 2 部分)

在上一个技能中,我们提到许多 PowerShell 用户更喜欢在线帮助,而不是本地下载的帮助。要默认使用联机帮助文档,请在新的 PowerShell 控制台中尝试以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# by default, -? opens LOCAL help
PS> dir -?

NAME
Get-ChildItem

SYNOPSIS
Gets the items and child items in one or more specified locations.


SYNTAX
...


# with this line you tell PowerShell to use the ONLINE help by default
PS> $PSDefaultParameterValues.Add("Get-Help:Online",$true)

# now, whenever you use -?, the ONLINE help opens in a nicely formatted browser window
PS> dir -?

使用 $PSDefaultParameterValues.Add("Get-Help:Online",$true) 命令告诉 PowerShell,Get-Help 命令应始终自动使用 -Online 参数,因此现在您始终可以获得基于浏览器的帮助。

尽管在大多数情况下这很好,但是联机帮助无法自动显示主题。如果需要显示有关主题的信息,只需使用 Get-Help 或带有 -ShowWindow 参数的帮助以显示本地帮助:

1
2
3
4
5
6
# this fails when help defaults to show ONLINE help
PS> help about_for
Get-Help : The online version of this Help topic cannot be displayed because the Internet address (URI) of the Help topic is not specified in the command code or in the help file for the command.

# the -ShowWindow parameter always shows local help in an extra window
PS C:\> help about_for -ShowWindow

PowerShell 技能连载 - 使用在线帮助(第 1 部分)

PowerShell 支持本地帮助文件和联机资源。请看区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# outputs help in same console window
# level of detail depends on whether local help was
# downloaded using Update-Help
PS C:\> help -Name Get-Process

NAME
Get-Process

SYNOPSIS
Gets the processes that are running on the local computer or a remote computer.


SYNTAX
Get-Process [[-Name] <String[]>] [-ComputerName <String[]>] [-FileVersionInfo] [-Module] []
...

# opens help in separate browser window
PS> help -Name Get-Process -Online

默认情况下,”help“(Get-Help 的别名)将帮助信息输出到 PowerShell 的输出窗口中。指定 -Online 开关参数时,浏览器会在单独的窗口中显示帮助文件。在线帮助文​​档采用了较好的格式,并且可以通过“复制”按钮轻松地复制和粘贴示例代码。并且由于联机文档是直接从 Microsoft 加载的,因此它们始终是最新的,因此无需通过 Update-Help 下载帮助。

这就是为什么许多用户喜欢在线帮助资源而不是本地帮助的原因。

线上还有许多有用的“关于”主题。关于主题涵盖了 PowerShell 语言和引擎的各个方面。您会在此处找到所有有关主题的很好的分类:https://docs.microsoft.com/zh-cn/powershell/module/microsoft.powershell.core/about/about

PowerShell 技能连载 - 在没有管理员特权的情况下更新帮助

在 Windows PowerShell 中,由于设计缺陷,更新帮助曾经需要管理员权限:帮助必须存储在模块所在的位置。更新 Windows 文件夹中存储的 Microsoft 模块的帮助需要对 Windows 文件夹的写权限。这就是普通用户无法下载和使用本地 PowerShell 帮助的原因。

在 PowerShell 7 中,此设计缺陷已得到纠正,现在可以将帮助安全地存储在用户配置文件中。不再需要涉及 PowerShell 模块的安装文件夹。

在 PowerShell 7 中使用 -Verbose 参数运行 Update-Help 以查看更改:

1
2
3
4
5
6
7
8
9
10
11
PS> Update-Help -Verbose
VERBOSE: Resolving URI: "https://go.microsoft.com/fwlink/?LinkId=717973"
VERBOSE: Your connection has been redirected to the following URI:
"https://pshelpprod.blob.core.windows.net/cabinets/powershell-5.1/"
VERBOSE: Performing the operation "Update-Help" on target "Microsoft.PowerShell.LocalAccounts, Current Version: 5.2.0.0, Available Version: 5.2.0.0, UICulture: en-US".
VERBOSE: Microsoft.PowerShell.LocalAccounts: Updated C:\Users\USERNAME\Dokumente\PowerShell\Help\Microsoft.PowerShell.LocalAccounts\1.0.0.0\en-US\Microsoft.Powershell.LocalAccounts.dll-Help.xml. Culture en-US Version 5.2.0.0
VERBOSE: Resolving URI: "https://go.microsoft.com/fwlink/?linkid=2113632"
VERBOSE: Your connection has been redirected to the following URI: "https://pshelp.blob.core.windows.net/powershell/help/7.0/Microsoft.PowerShell.Management/"
VERBOSE: Performing the operation "Update-Help" on target "Microsoft.PowerShell.Management, Current Version: 7.0.1.0, Available Version: 7.0.1.0, UICulture: en-US".
VERBOSE: Microsoft.PowerShell.Management: Updated C:\Users\USERNAME\Dokumente\PowerShell\Help\en-US\Microsoft.PowerShell.Commands.Management.dll-Help.xml. Culture en-US Version 7.0.1.0
...

要使用下载的本地帮助文件,您可以在想了解的命令后添加 “-?“ 通用参数:

1
2
3
4
5
6
7
8
PS> Get-Process -?

NAME
Get-Process

SYNOPSIS
Gets the processes that are running on the local computer or a remote computer.
...

如果您之前未下载本地帮助文件,则 “-“ 参数仅显示有限的语法帮助。

PowerShell 技能连载 - PowerShell技能连载-检查配置文件脚本(第 2 部分)

在上一个脚本中,我们介绍了一一行代码,用于检查哪些配置文件脚本是存在的。但是,此解决方案仅只适用于单个宿主,因为每个宿主都使用自己的特定于宿主的配置文件路径。

这是一种更通用的方法:它列出了系统上存在的所有 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
# calculate the parent paths that can contain profile scripts
$Paths = @{
AllUser_WPS = $pshome
CurrentUser_WPS = Join-Path -Path ([Environment]::GetFolderPath('MyDocuments')) -ChildPath "WindowsPowerShell"
AllUser_PS = "$env:programfiles\PowerShell\*"
CurrentUser_PS = Join-Path -Path ([Environment]::GetFolderPath('MyDocuments')) -ChildPath "PowerShell"
}

# check all paths for PowerShell scripts ending on "profile.ps1"
$Paths.Keys | ForEach-Object {
$key = $_
$path = Join-Path -Path $paths[$key] -ChildPath '*profile.ps1'
Get-ChildItem -Path $Path |
ForEach-Object {
# create a custom object with all relevant details for any
# found profile script

# name of PowerShell host is the prefix of profile file name
if ($_.Name -like '*_*')
{
$hostname = $_.Name.Substring(0, $_.Name.Length-12)
}
else
{
$hostname = 'any'
}
[PSCustomObject]@{
# scope and PowerShell version is found in the
# name of the parent folder
Scope = $key.Split('_')[0]
PowerShell = $key.Split('_')[1]

Host = $hostname
Path = $_.FullName
}
}
}

结果报告了所有主机的现有 PowerShell 配置文件脚本,看起来可能与此类似:

Scope       PowerShell Host                    Path
-----       ---------- ----                    ----
CurrentUser WPS        Microsoft.PowerShellISE C:\Users\tobia\OneDrive\Dokumente\WindowsPowerShell\Microsoft.PowerShellISE_...
CurrentUser WPS        any                     C:\Users\tobia\OneDrive\Dokumente\WindowsPowerShell\profile.ps1
CurrentUser PS         Microsoft.VSCode        C:\Users\tobia\OneDrive\Dokumente\PowerShell\Microsoft.VSCode_profile.ps1