PowerShell 技能连载 - 对循环启用流操作

PowerShell 包含许多循环构造。这些循环构造不能变成流,所以您无法将结果通过管道传递给其它 cmdlet 并且享受管道的实时性优势。相反,您必须先将所有数据存储在变量中,并且只有当循环结束之后才可以将变量通过管道传递给其它命令。

虽然您可以用 ForEach-Object 来代替传统的 foreachfor 循环,但它会减慢代码的执行速度,并且不是代替 whiledo 循环的方案。

以下是一个对所有循环启用快速流的简单技巧。让我们从这个非常简单的循环开始:

1
2
3
4
5
6
7
8
9
# stupid sample using a do loop
# a more realistic use case could be a database query

do
{
$val = Get-Random -Minimum 0 -Maximum 10
$val

} while ($val -ne 6)

它会一直循环直到达到随机数 6。您会发现无法实时将结果传输到管道,所以你必须使用类似这样的方法来对结果进行排序或者用它们来做其他事情:

1
2
3
4
5
6
7
8
$all = do
{
$val = Get-Random -Minimum 0 -Maximum 10
$val

} while ($val -ne 6)

$all | Out-GridView

通过将循环封装到一个脚本块中,您可以获得实时流:更少的内存消耗,立即得到结果:

1
2
3
4
5
6
& { do
{
$val = Get-Random -Minimum 0 -Maximum 10
$val

} while ($val -ne 6) } | Out-GridView

PowerShell 技能连载 - 开发 PowerShell Core 还是 Windows PowerShell 脚本

您可能知道,有两种类型的 PowerShell:随着 Windows 操作系统分发的 Windows PowerShell 是基于完整的 .NET Framework而 PowerShell 6 以及更高版本是开源、跨平台,并且基于(有限的).NET Core 和 Standard。

如果您开发能够在两类系统上执行的脚本,那非常棒!不过如果如果知道您的代码是基于其中的一个系统,请确保在脚本的顶部添加合适的 #requires 语句。

这段代码只能在 PowerShell 6 及更高版本运行(假设您已事先将它保存为文件):

1
2
#requires -PSEdition Core
"This runs in PowerShell 6 and better only..."

类似地,以下代码只能在 Windows PowerShell 中运行:

1
2
#requires -PSEdition Desktop
"This runs in Windows PowerShell only..."

PowerShell 技能连载 - 控制音量和静音状态

要调节音量以及静音、取消扬声器的静音,PowerShell 可以像这样用 C# 代码来操作 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
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
Add-Type -TypeDefinition @'
using System.Runtime.InteropServices;
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IAudioEndpointVolume {
// f(), g(), ... are unused COM method slots. Define these if you care
int f(); int g(); int h(); int i();
int SetMasterVolumeLevelScalar(float fLevel, System.Guid pguidEventContext);
int j();
int GetMasterVolumeLevelScalar(out float pfLevel);
int k(); int l(); int m(); int n();
int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, System.Guid pguidEventContext);
int GetMute(out bool pbMute);
}
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDevice {
int Activate(ref System.Guid id, int clsCtx, int activationParams, out IAudioEndpointVolume aev);
}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDeviceEnumerator {
int f(); // Unused
int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
}
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }
public class Audio {
static IAudioEndpointVolume Vol() {
var enumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
IMMDevice dev = null;
Marshal.ThrowExceptionForHR(enumerator.GetDefaultAudioEndpoint(/*eRender*/ 0, /*eMultimedia*/ 1, out dev));
IAudioEndpointVolume epv = null;
var epvid = typeof(IAudioEndpointVolume).GUID;
Marshal.ThrowExceptionForHR(dev.Activate(ref epvid, /*CLSCTX_ALL*/ 23, 0, out epv));
return epv;
}
public static float Volume {
get {float v = -1; Marshal.ThrowExceptionForHR(Vol().GetMasterVolumeLevelScalar(out v)); return v;}
set {Marshal.ThrowExceptionForHR(Vol().SetMasterVolumeLevelScalar(value, System.Guid.Empty));}
}
public static bool Mute {
get { bool mute; Marshal.ThrowExceptionForHR(Vol().GetMute(out mute)); return mute; }
set { Marshal.ThrowExceptionForHR(Vol().SetMute(value, System.Guid.Empty)); }
}
}
'@

# query audio volume and mute status
[Audio]::Volume
[Audio]::Mute

# unmute
[Audio]::Mute = $false

# set volume to 50% (range 0.0 - 1.0)
[Audio]::Volume = 0.5]

PowerShell 技能连载 - 检查坏(不安全的)密码(第 2 部分)

在前一个技能中我们解释了如何用 Web Service 来安全地检测密码并且查明它们是否已被泄漏。

信息安全有关的代码有时经过压缩后看起来是否“有趣”,所以在第一步分钟我们分享了优美的而且可读的代码。而以下是考虑“信息安全”的变体,它展示 PowerShell 代码可以被压缩到什么程度并且可以自动混淆。这段代码返回一个指定的密码被暴露了多少次(如果未曾发现被暴露过,返回 null)。

1
$p = 'P@ssw0rd'[Net.ServicePointManager]::SecurityProtocol = 'Tls12'$a,$b = (Get-FileHash -A 'SHA1' -I ([IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($p)))).Hash -split '(?<=^.{5})'(((irm "https://api.pwnedpasswords.com/range/$a" -UseB) -split '\r\n' -like "$b*") -split ':')[-1

PowerShell 技能连载 - 检查坏(不安全的)密码(第 1 部分)

复杂的密码不一定安全。例如,”P@ssw0rd” 是一个非常复杂的密码,但是非常不安全。这是为什么安全社区开始建议用更相关的测试取代复杂性标准,并防止使用以前黑客入侵中使用过的密码。这些密码——虽然它们可能很复杂——是字典攻击的一个常规部分并且非常不安全。

如何知道某个密码是否已被泄露?您可以使用类似 haveibeenpwnd.com 的网站或者它们的 API。这是它的工作原理:

  1. 您根据密码创建哈希值,这样就不会泄漏您的密码。
  2. 将哈希的头五个字节发送到 API,这样就不会泄漏哈希值。
  3. 您可以获得所有以这五个字节开头的哈希值。
  4. 检查返回的哈希中是否有您密码的哈希值。

以下是用 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
# enable all SSL protocols
[Net.ServicePointManager]::SecurityProtocol = 'Ssl3,Tls, Tls11, Tls12'

# get password hash
$stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($Password))
$hash = Get-FileHash -InputStream $stream -Algorithm SHA1
$stream.Close()
$stream.Dispose()

# find first five and subsequent hash characters
$prefix, $suffix = $hash.Hash -split '(?<=^.{5})'

# ask for matching passwords with the same first 5 hash digits
$url = "https://api.pwnedpasswords.com/range/$prefix"
$response = Invoke-RestMethod -Uri $url -UseBasicParsing

# find the exact match
$lines = $response -split '\r\n'
$seen = foreach ($line in $lines)
{
if ($line.StartsWith($suffix)) {
[int]($line -split ':')[-1]
break
}
}

"$Password has been seen {0:n0} times." -f $seen

试着改变 $Password 中的密码来测试这段代码。您会很惊讶地发现许多密码已经泄漏:

Sunshine has been seen 13.524 times.

PowerShell 技能连载 - 用聪明的方法指定位标志

在前一个技能中我们学习了如何在 PowerShell 中启用所有的 SSL 安全协议来连接到 Web Service 和 网站:

1
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3 -bor [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12

有趣的是,可以用一行更短的代码来代替:

1
[Net.ServicePointManager]::SecurityProtocol = 'Ssl3, Tls, Tls11, Tls12'

以下是它的原因:

由于 SecurityProtocolNet.SecurityProtocolType 类型的,所以当您传入字符串数据,它可以自动转换:

1
2
PS> [Net.ServicePointManager]::SecurityProtocol.GetType().FullName
System.Net.SecurityProtocolType

与其用 SecurityProtocolType 枚举并且用 -bor 操作符来连接,您还可以用比特标志位的名称组成的逗号分隔的字符串。两者是相同的:

1
2
3
4
5
6
7
$a = [Net.SecurityProtocolType]::Ssl3 -bor [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
$b = [Net.SecurityProtocolType]'Ssl3, Tls, Tls11, Tls12'



PS> $a -eq $b
True

PowerShell 技能连载 - 在 PowerShell 中使用 SSL/HTTPS

根据您的 PowerShell、.NET Framework 的版本和升级,WEB 连接的缺省安全协议可能仍然是 SSL3。您可以方便地查明它:

1
[Net.ServicePointManager]::SecurityProtocol

如果返回的协议不包含 Tls12,那么您可能无法用 PowerShell 连接到安全的 Web Service 和网站。我们只需要这样操作就可以启用更多的服务:

1
2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3 -bor [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
[Net.ServicePointManager]::SecurityProtocol

PowerShell 技能连载 - 以固定宽度分割文本

假设您需要以固定宽度分割一段文本。例如,如果您需要一段文本的前 5 个字符,以及剩余的部分,如何实现它?

大多数 PowerShell 用户可能会用类似这样的方法:

1
$text = 'ID12:Here is the text'$prefix = $text.Substring(0,5)$suffix = $text.Substring(5)$prefix$suffix

当然,如果用分割字符,例如 “:”,可以这样操作:

1
$prefix, $suffix = 'ID12:Here is the text' -split ':'$prefix$suffix

然而,这将会吃掉分割字符,并且它会导致超过两个部分。这不是我们要的目标:用固定宽度分割一段文本。而您仍然可以使用 -split 操作符:

1
$prefix, $suffix = 'ID12:Here is the text' -split '(?<=^.{5})'$prefix$suffix

正则表达式结构 “(?<=XXX)“ 称为“向后引用”。”^“ 代表文本的开始,而 “.“ 代表任何字符。如您猜测的那样,”{5}“ 限定该占位符出现的次数,所以基本上这个正则表达式从剩下的文本中分割出前 5 个字符并且返回两部分(假设文本至少 6 个以上字符长度)。

PowerShell 技能连载 - 获取文本的哈希值

在 PowerShell 5(以及 Get-FileHash 之前),要计算字符串和文件的哈希值,您需要借助原生的 .NET 方法。以下是一段为一个字符串创建 MD5 哈希的示例代码:

1
2
3
4
5
6
7
8
9
10
11
$Text = 'this is the text that you want to convert into a hash'

$Provider = New-Object -TypeName Security.Cryptography.MD5CryptoServiceProvider
$Encodiner = New-Object -TypeName Text.UTF8Encoding

$Bytes = $Encodiner.GetBytes($Text)
$hashBytes = $Provider.ComputeHash($Bytes)
$hash = [System.BitConverter]::ToString($hashBytes)

# remove dashes if needed
$hash -replace '-'

如果您需要计算一个文件内容的哈希值,要么使用 Get-Content 来读取文件,或者使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
$Path = "C:\somefile.txt"

# use your current PowerShell host as a sample
$Path = Get-Process -Id $Pid | Select-Object -ExpandProperty Path

$Provider = New-Object -TypeName Security.Cryptography.MD5CryptoServiceProvider
$FileContent = [System.IO.File]::ReadAllBytes($Path)
$hashBytes = $Provider.ComputeHash($FileContent)
$hash = [System.BitConverter]::ToString($HashBytes)

$hash -replace '-'

PowerShell 技能连载 - 从文本创建哈希

哈希是一种唯一确定一段文本而不用暴露原始文本的棒法。哈希被用来确定文本、查找重复的文件内容,以及验证密码。PowerShell 5 以及更高版本甚至提供了一个 cmdlet 来计算文件的哈希值:Get-FileHash

然而,Get-FileHash 不能计算字符串的哈希。没有必要只是为了计算哈希值而将字符串保存到文件。您可以使用所谓的内存流来代替。以下是一段从任何字符串计算哈希值的代码片段:

1
2
3
4
5
6
7
8
$Text = 'this is the text that you want to convert into a hash'

$stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($Text))
$hash = Get-FileHash -InputStream $stream -Algorithm SHA1
$stream.Close()
$stream.Dispose()

$hash

使用完成后别忘了关闭并释放内存流,防止内存泄漏并释放所有资源。