PowerShell 技能连载 - 简易的 PowerShell 聊天室

以下是一个有趣的 PowerShell 脚本,您可以用它来创建一个简易的多频道聊天室。您所需的只是一个所有人都有读写权限的网络共享目录。

该聊天室是基于文件的,并且使用 PowerShell 的功能来监视文件的改变。所以基本上,每个聊天频道是一个文本文件,并且无论何时,如果某人想“说”一些内容,那么就会向文件中添加一行。任何连接到该聊天频道的人实际上都在监视这个文件的改变。

显然,这个“聊天室”只适用于试验,并且有许多限制。例如,当某人在写文件时,其他人无法同时写入。但是,它很好地说明了 PowerShell 如何监视文件并在添加新文本时采取行动。您也可以将这个技术用在日志文件上:PowerShell 可以在日志文件有新内容的时候通知您,并且甚至自动过滤新增加的文本并根据触发关键字发出警告或采取行动。

在开始之前,请确保调整 ServerShare 并将它设置为一个可读写的网络共享目录。

下一步,您可以这样进入聊天室:

1
Enter-Chat -ChatChannelName lunchbreak -Name Tobias -ShowOldPosts

-ShowOldPosts 显示已有的聊天信息。如果没有添加这个参数,那么只能看见新的信息。无论何时运行 Enter-Chat,它都会检查在 -ChatChannelName 中指定名称的文件,如果该文件不存在,就会创建它。

Get-ChatChannel 列出共享目录中的所有聊天文件,以及聊天室最后使用的时间。该信息完全取自文件属性 (LastWriteTime)。

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
74
75
76
77
78
79
# make sure you adjust this path
# it must point to a network share where you have read and write permissions
$ServerShare = "\\myserver\chathome"

function Enter-Chat
{
param
(
[Parameter(Mandatory)]
[string]
$ChatChannelName,

[string]
$Name = $env:USERNAME,

[Switch]
$ShowOldPosts,

$HomeShare = $ServerShare

)

if ($ShowOldPosts)
{
$Option = ''
}
else
{
$Option = '-Tail 0'
}

$Path = Join-Path -Path $HomeShare -ChildPath "$ChatChannelName.txt"
$exists = Test-Path -Path $Path
if ($exists -eq $false)
{
$null = New-Item -Path $Path -Force -ItemType File
}

$process = Start-Process -FilePath powershell -ArgumentList "-noprofile -windowstyle hidden -command Get-COntent -Path '$Path' $Option -Wait | Out-GridView -Title 'Chat: [$ChatChannelName]'" -PassThru

Write-Host "To exit, enter: quit"
"[$Name entered the chat]" | Add-Content -Path $Path
do
{
Write-Host "[$ChatChannelName]: " -ForegroundColor Green -NoNewline
$inputText = Read-Host

$isStopCommand = 'quit','exit','stop','leave' -contains $inputText
if ($isStopCommand -eq $false)
{
"[$Name] $inputText" | Add-Content -Path $Path
}


} until ($isStopCommand -eq $true)
"[$Name left the chat]" | Add-Content -Path $Path

$process | Stop-Process
}



function Get-ChatChannel
{
param
(
$HomeShare = $ServerShare

)

Get-ChildItem -Path $HomeShare -Filter *.txt -File |
ForEach-Object {
[PSCustomObject]@{
ChannelName = [System.IO.Path]::GetFileNameWithoutExtension($_.Name)
LastActive = $_.LastWriteTime
Started = $_.CreationTime
}
}
}

PowerShell 技能连载 - 测试

在之前的技能中,我们讨论过了类似 haveIbeenpwned.com 的服务。它们从之前的黑客攻击中收集泄露的密码,这样您就可以检查您的密码是否已被泄露,并可能被包括在未来的字典攻击中。

以下是两个有用的函数:Test-Password 请求一个 SecureString。当您收到提示时,输入将会被屏蔽。接下来 Convert-SecureStringToText 将会将整个密码转为纯文本。然后 Test-Password 将把输入的整个密码进行哈希运算,并只发送前 5 个字节到 WEB 服务。您的原始密码永远不会泄露。

Web 服务将返回所有以该 5 个字节开头的泄露的密码哈希,这样您就可以检查返回的哈希中是否包含您的哈希。

如果您的密码在之前的攻击中出现,那么您将受到它在攻击中出现的次数。任何返回大于 0 的数字的密码都必须视为不安全的,并且不该使用。

请记住:当某个密码返回一个大于 0 的数字时,这并不意味着您的密码被破解了。它的意思是,在世界上所有地方中,您的密码在攻击中出现,所以要么您或者其他人使用它并且被黑客攻击:该密码现在不安全并且已成为攻击字典的一部分,黑客会尝试账户。如果您继续使用这个密码,黑客有可能在不就得将来利用简单(快速)的字典攻击来破解它。

在之前的提示中,我们已经讨论了haveIbeenpwned.com等服务。他们从以前的黑客攻击中收集泄露的密码,这样你就可以检查你的密码是否已被泄露,并可能被包括在未来的字典攻击中。

下面有两个有用的函数:Test-Password请求SecureString,因此当您收到提示时,您的输入将被屏蔽。然后将输入的密码转换为纯文本。然后,Test-Password散列您输入的密码,只将前5个字节发送给web服务。您的原始密码永远不会泄露。

web服务将返回所有以您的5个字节开头的密码散列,因此您可以检查返回的散列是否与您的散列匹配。

如果您的密码在以前的攻击中被发现,您将收到它参与攻击的次数。任何返回大于0的数字的密码都被认为是不安全的,不应该使用。

请记住:当密码返回一个大于0的数字时,这并不意味着您自己的密码被破解了。它的意思是,在世界上任何地方,你的密码在攻击中出现,所以要么你或其他人使用它并被黑客攻击。无论是你还是别人:这个密码现在是不安全的,因为它成为黑客攻击字典的一部分,黑客会尝试帐户。如果你继续使用这个密码,黑客很有可能在不久的将来利用简单(快速)的字典攻击来破解它。

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
function Convert-SecureStringToText{
param (
[Parameter(Mandatory,ValueFromPipeline)]
[System.Security.SecureString]
$Password )

process {
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
}
}function Test-Password
{ [CmdletBinding()]
param (
[Parameter(Mandatory, Position=0)]
[System.Security.SecureString]
$Password )

$plain = $Password | Convert-SecureStringToText
$bytes = [Text.Encoding]::UTF8.GetBytes($plain)
$stream = [IO.MemoryStream]::new($bytes)
$hash = Get-FileHash -Algorithm 'SHA1' -InputStream $stream $stream.Close()
$stream.Dispose()

$first5hashChars,$remainingHashChars = $hash.Hash -split '(?<=^.{5})'
$url = "https://api.pwnedpasswords.com/range/$first5hashChars" [Net.ServicePointManager]::SecurityProtocol = 'Tls12' $response = Invoke-RestMethod -Uri $url -UseBasicParsing
$lines = $response -split '\r\n' $filteredLines = $lines -like "$remainingHashChars*"
[int]($filteredLines -split ':')[-1]
}

PowerShell 技能连载 - 将 SecureString 转换为文本

将加密的 SecureString 转换回纯文本非常有用。例如,通过这种方式,您可以使用 PowerShell 的“遮罩输入”特性。只需请求一个 SecureString, PowerShell 就会屏蔽用户输入的显示。接下来,将 SecureString 转换成纯文本,这样就可以在内部使用它来做任何您想做的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Convert-SecureStringToText
{
param
(
[Parameter(Mandatory,ValueFromPipeline)]
[System.Security.SecureString]
$Password
)

process
{
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
}
}

$password = Read-Host -Prompt 'Enter password' -AsSecureString
$plain = Convert-SecureStringToText -Password $password

"You entered: $plain"

如果你想知道为什么一开始就可以将 SecureString 转换成纯文本,那么请注意:SecureString 只保护第三方的字符串内容,即“中间人”攻击。它绝不是保护真正输入密码的人。

这也体现了为什么使用凭据有风险。当一个脚本请求凭据时,脚本总是可以获取整个密码:

1
2
3
$cred = Get-Credential -UserName $env:USERNAME -Message 'Enter your password'
$plain = Convert-SecureStringToText -Password $cred.Password
"You entered: $plain"

实际上,通过凭据对象,要获取输入的密码更容易,因为它有一个内置的方法实现这个功能:

1
2
3
$cred = Get-Credential -UserName $env:USERNAME -Message 'Enter your password'
$plain = $cred.GetNetworkCredential().Password
"You entered: $plain"

当一个脚本请求凭据时,请确保信任该脚本(的作者)。

PowerShell 技能连载 - 在 PowerShell 脚本中嵌入二进制文件(图片、DLL)

如果脚本需要外部二进制资源,例如图片或者 DLL 文件,您当然可以将它们和脚本一起分发。不过,您也可以将这些二进制文件作为文本嵌入您的脚本文件:

  • 以字节方式读取二进制文件
  • 将字节保存为 Base64 编码的字符串

通过这种方式,您的脚本可以从一个文本变量中读取 Base64 编码的二进制,然后将数据转换回字节,并且将它们写入临时文件。

以下是两个演示该概念的函数:

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
function Convert-BinaryToText
{
param
(
[Parameter(Mandatory)]
[string]
$Path
)

$Bytes = [System.IO.File]::ReadAllBytes($Path)
[System.Convert]::ToBase64String($Bytes)
}

function Convert-TextToBinary
{
param
(
[Parameter(Mandatory)]
[string]
$Text,

[Parameter(Mandatory)]
[string]
$OutputPath
)

$Bytes = [System.Convert]::FromBase64String($Text)
[System.IO.File]::WriteAllBytes($OutputPath, $Bytes)
}

Convert-BinaryToText 接受一个任意文件的路径,并返回 Base64 编码的字符串。Convert-BinaryToText 做的是相反的操作:传入 Base64 编码的字符串以及一个目标路径,该函数将自动重建二进制文件。

请注意将二进制文件保存成 Base64 字符串并不能节省空间。您的脚本的大小可能会超过原来的二进制文件大小。但是,即使 Base64 编码的字符串很大,PowerShell也能很好地处理它们,而且从 Base64 编码的字符串中提取二进制文件非常快。

PowerShell 技能连载 - 使用 Windows 10 内置的 SSH 支持

在 2018 年 10 月份,一个 Windows 10 更新加入了内置的 SSH 支持。从此之后,Windows 10 附带了一个名为 “ssh” 的命令行工具。您可以在 PowerShell 中使用它来连接到其它设备(包括 IoT、树莓派等设备)而不需要第三方工具:

1
2
3
4
5
6
7
8
9
PS> ssh
usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]
[-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
[-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]
[-i identity_file] [-J [user@]host[:port]] [-L address]
[-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
[-Q query_option] [-R address] [-S ctl_path] [-W host:port]
[-w local_tun[:remote_tun]] destination [command]
PS>

PowerShell 技能连载 - 使用最新版的 PowerShell Core

在前一个技能中我们演示了如何下载一个 PowerShell 脚本,用来自动下载最新版的 PowerShell Core。

这个脚本支持一系列参数。默认情况下,它获取最新(稳定版)的生产版本。如果您希望使用包括预览版的最新版,请使用 -Preview 参数。并且,如果您希望使用 MSI 包安装它,请加上 -MSI 参数。

您不一定要将该脚本保存到文件。您可以直接执行下载脚本并通过 Invoke-Expression 执行它。不过,这个 cmdlet 被认为是有风险的,因为它直接执行您提交的任何代码,而没有机会让您事先检查代码。

以下是一行示例代码,用来下载最新版的 PowerShell Core MSI 安装包:

1
Invoke-Expression -Command "& { $(Invoke-RestMethod https://aka.ms/install-powershell.ps1) } -UseMSI -Preview"

PowerShell 技能连载 - 安装 PowerShell Core

您也许知道,Windows PowerShell(随 Windows 分发的)已成为过去,所有的工作都投入到新的跨平台 PowerShell Core 的开发中。新版的 PowerShell 还没有在 Windows 中提供开箱即用的功能,所以如果要使用它,您需要手动下载。

幸运的是,有一个脚本可以为您完成繁重的工作。这是用来下载该脚本的代码:

1
Invoke-RestMethod https://aka.ms/install-powershell.ps1

如果您希望将该脚本保存到一个文件,请使用以下代码:

1
2
3
4
$Path = "$home\desktop\installps.ps1"

Invoke-RestMethod https://aka.ms/install-powershell.ps1 | Set-Content -Path $Path -Encoding UTF8
notepad $Path

这将下载该脚本并在一个记事本中打开该脚本。该脚本文件存放在桌面上,这样您可以用鼠标右键单击它并使用 PowerShell 执行它来下载并安装最新生产版的 PowerShell Core。

PowerShell 技能连载 - 查找公网 IP 地址

这是一个单行程序,检索您当前的公共IP地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> Invoke-RestMethod -Uri http://ipinfo.io


ip : 87.153.224.209
hostname : p5799e0d1.dip0.t-ipconnect.de
city : Hannover
region : Lower Saxony
country : DE
loc : 52.3705,9.7332
org : AS3320 Deutsche Telekom AG
postal : 30159
timezone : Europe/Berlin
readme : https://ipinfo.io/missingauth

PowerShell 技能连载 - 实时日志处理

PowerShell 提供了一种强大而简单的方法来监视文件更改。假设您有一个经常更改的日志文件。以下是一个PowerShell脚本,用于监视日志文件的更改。每当发生更改时,都会执行一些代码:

1
2
3
4
5
6
7
# make sure this points to a log file
$Path = '\\myserver\report2.txt'

Get-Content -Path $Path -Tail 0 -Wait |
ForEach-Object {
"Detected $_"
}

只要确保修改 $path 指向某个实际的日志文件。每当向文件附加文本(并且保存更改),ForEach-Object 循环都会执行脚本块并输出 “Detected “。通过这种方式,您可以方便地响应实际的改变。

Get-Content 完成繁重的工作:-Wait 启用内容监视,-Tail 0 确保忽略现有内容,只查找新添加的文本。

PowerShell 技能连载 - 检测键盘按键

通常,只有在真正的控制台窗口中才支持按键检测,因此这种方法不适用于 PowerShell ISE 和其他 PowerShell 宿主。

但是,PowerShell 可以从 Windows Presentation Foundation 中借用一种类型,这种类型可以检查任何键的状态。这样,实现在任何 PowerShell 脚本中都可以工作的“退出”键就变得很简单了,无论是在控制台、Visual Studio Code 还是 PowerShell ISE 中运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName PresentationCore

# choose the abort key
$key = [System.Windows.Input.Key]::LeftCtrl

Write-Warning "PRESS $key TO ABORT!"

do
{
$isCtrl = [System.Windows.Input.Keyboard]::IsKeyDown($key)
if ($isCtrl)
{
Write-Host
Write-Host "You pressed $key, so I am exiting!" -ForegroundColor Green
break
}
Write-Host "." -NoNewline
Start-Sleep -Milliseconds 100
} while ($true)

只需要在变量 $key 中选择“退出”按键即可。本例使用的是左 CTRL 键。