PowerShell 技能连载 - 使用一个停表
在 PowerShell 中,要测量时间,您可以简单将一个 datetime 值减去另一个 datetime 值:
1 | $Start = Get-Date |
一个优雅的实现是用停表:
1 | $StopWatch = [Diagnostics.Stopwatch]::StartNew() |
使用停表的好处是可以暂停和继续。
在 PowerShell 中,要测量时间,您可以简单将一个 datetime 值减去另一个 datetime 值:
1 | $Start = Get-Date |
一个优雅的实现是用停表:
1 | $StopWatch = [Diagnostics.Stopwatch]::StartNew() |
使用停表的好处是可以暂停和继续。
类似 $MyInvocation.MyCommand.Definition
的代码对于确定当前脚本存储的位置十分有用,例如需要存取同一个文件夹下其它资源的时候。
然而,从 PowerShell 3 开始,有一个更简单的替代方式可以查找当前脚本的名称和/或当前脚本文件夹的路径。请自己运行以下代码测试:
1 | $MyInvocation.MyCommand.Definition |
如果您交互式运行这段代码(或在一个“无标题”脚本中),它们都不会返回任何内容。但是当您将脚本保存后执行,这两行代码将返回脚本的路径,并且后两行代码将返回脚本所在文件夹的路径。
$PSCommandPath
和 $PSScriptRoot
的好处在于它们总是包含相同的信息。相比之下,$MyInvocation
可能会改变,而且当从一个函数中读取这个变量时,它就会发生改变。
1 | function test |
现在,$MyInvocation
变得没有价值,因为它重视返回调用本脚本块的调用者信息。
以下是一段连接到本地防火墙并转储所有打开的防火墙端口的 PowerShell 代码:
1 | $firewall = New-object -ComObject HNetCfg.FwPolicy2 |
结果看起来类似这样:
Name ApplicationName LocalPorts
---- --------------- ----------
pluginhost.exe C:\users\tobwe\appdata\local\skypeplugin\pluginhost.exe *
pluginhost.exe C:\users\tobwe\appdata\local\skypeplugin\pluginhost.exe *
spotify.exe C:\users\tobwe\appdata\roaming\spotify\spotify.exe *
spotify.exe C:\users\tobwe\appdata\roaming\spotify\spotify.exe *
在 Windows 10 和 Server 2016 中,有一系列现成的跟防火墙有关的 cmdlet:
1 | PS> Get-Command -Noun *Firewall* |
在前一个技能中我们通过 PowerShell 后台作业实现了超时机制,这样您可以设置某段代码执行的最大允许时间,超过该时间将抛出一个异常。
以下是一个更轻量级的替代,它使用进程内线程,而不是进程外执行:
1 | function Invoke-CodeWithTimeout |
以下是使用该新超时机制的方法:
1 | PS> Invoke-CodeWithTimeout -Code { Start-Sleep -Seconds 6; Get-Date } -Timeout 5 |
如果希望某些代码不会无限执行下去,您可以使用后台任务来实现超时机制。以下是一个示例函数:
1 | function Invoke-CodeWithTimeout |
所以基本上说,要让代码执行的时间不超过 5 秒,请试试以下代码:
1 | PS> Invoke-CodeWithTimeout -Code { Start-Sleep -Seconds 6; Get-Date } -Timeout 5 |
该方法有效。但是,它所使用的作业相关的开销相当大。创建后台作业并将数据返回到前台任务的开销可能增加了额外的时间。所以我们将在明天的技能中寻求一个更好的方法。
当 Powershell 脚本携带一个数字签名后,您可以快速地找出是谁对这个脚本签的名,更重要的是,这个脚本是否未被篡改。在本系列之前的部分中,您学习了如何创建数字签名,以及如何对 PowerShell 文件签名。现在我们来看看如何验证脚本的合法性。
1 | # this is the path to the scripts you'd like to examine |
只需要调整路径,该脚本讲查找该路径下的所有 PowerShell 脚本,然后检查它们的签名。结果有如下可能性:
NotSigned: 没有签名
UnknownError: 使用非受信的证书签名
HashMismatch: 签名之后修改过
Valid: 采用受信任的证书签名,并且没有改动过
在将 PowerShell 脚本发送给别人之前,最好对它进行数字签名。签名的角色类似脚本的“包装器”,可以帮助别人确认是谁编写了这个脚本以及这个脚本是否仍是原始本版本,或是已被篡改过。
要对 PowerShell 脚本签名,您需要一个数字代码签名证书。在前一个技能中我们解释了如何创建一个该证书,并且/或者从 pfx 或证书存储中加载该证书。以下代码假设在 $cert
中已经有了一个合法的代码签名证书。如果还没有,请先阅读之前的技能文章!
1 | # make sure this PFX file exists or create one |
运行这段代码后,指定目录中的所有脚本都会添加上数字签名。如果您连接到了 Internet,应该考虑签名时使用时间戳服务器,并且将最后一行代码替换成这行:
1 | # apply signatures to all scripts in the folder |
使用时间戳服务器会减慢签名的速度但是确保不会用过期的证书签名:例如当某天一本证书过期了,但是签名仍然有效。因为官方的时间戳服务器,签名仍然有效,因为官方的时间戳服务器证明该签名是在证书过期之前应用的。
可以通过加载至 Windows 证书存储的方式永久地安装证书。PowerShell 可以通过 cert:
驱动器存取这个存储。以下这行代码将显示所有个人证书:
1 | PS C:\> Get-ChildItem -Path Cert:\CurrentUser\my |
请注意:如果您的个人证书存储是空的,您可能需要查看这个系列之前的文章来创建一些测试证书。
要只查看代码签名证书,请添加 -CodeSigningCert
动态参数。这将排除所有其它用途的证书以及没有私钥的证书:
1 | PS C:\> Get-ChildItem -Path Cert:\CurrentUser\my -CodeSigningCert |
证书是以它们唯一的指纹 ID 标识,它就像文件的名字一样:
1 | PS C:\> Get-ChildItem -Path Cert:\CurrentUser\my |
如果还不知道唯一的指纹 ID,您需要先找出它,因为这个 ID 能够唯一确认证书。一个查找的方法是按其它属性过滤,例如 subject:
1 | PS C:\> dir Cert:\CurrentUser\my | where subject -like *tobias* |
在本迷你系列的第一部分,已经介绍了如何用 PowerShell 创建新的证书。
现在您已经学习了如何从 pfx 文件和个人证书存储中读取已有的证书。
请关注下一个技能来学习如何使用证书来实际签名 PowerShell 代码!
在前一个技能中我们创建了新的代码签名测试证书,它既是一个 pfx 文件,同时也位于您的证书存储中。今天,我们将看看如何在 PowerShell 中加载这些(或来自其它来源的任意其它证书)。
要从 pfx 文件加载证书,请使用 Get-PfxCertificate
:
1 | $Path = "$home\desktop\tobias.pfx" |
Get-PfxCertificate
将提醒输入 pfx 文件创建时输入的密码。有一些 pfx 文件并没有使用密码保护或者是通过您的用户账户来保护证书,这些情况下不会显示提示。
如果您需要自动读取 pfx 证书,以下是一个通过参数输入密码,并且可以无人值守地从 pfx 文件读取证书:
1 | function Load-PfxCertificate |
以下是这个函数工作的方式:
1 | PS C:\> $pwd = 'secret' | ConvertTo-SecureString -AsPlainText -Force |
修改 Load-PfxCertificate
的最后一行,可以支持多于一个证书。改函数永远返回第一个证书 ($container[0]
),但是可以选择任意另一个下标。
请关注下一个技能,学习如何存取您个人证书存储中的证书。
要使用数字签名,以及探索如何对脚本和模块签名,您首先需要代码签名证书。如果您无法从公司的 IT 部门获取到代码签名证书,PowerShell 可以为您创建一个(假设您使用的是 Windows 10 或者 Server 2016)。
我们将这些细节封装为一个名为 New-CodeSigningCert
的易用的函数,它可以在个人证书存储中创建新的代码签名证书,并且以 pfx 文件的形式返回新创建的证书。
1 | function New-CodeSigningCert |
以下是如何以 pfx 文件的形式创建代码签名证书的方法:
1 | PS> New-CodeSigningCert -FriendlyName 'Tobias Code-Signing Test Cert' -Name TobiasCS -FilePath "$home\desktop\myCert.pfx" |
您将会收到提示,要求输入用来保护 pfx 文件的密码。请记住该密码,一会儿导入 pfx 文件的时候需要该密码。
以下是如何在个人证书存储中创建代码签名证书的方法:
1 | PS> New-CodeSigningCert -FriendlyName 'Tobias Code-Signing Test Cert' -Name TobiasCS -Trusted |
调用这个函数之后,您的证书现在位于 cert:
驱动器中,您现在可以像这样查看它:
1 | PS C:\> dir Cert:\CurrentUser\my |
同样地,您可以打开个人证书存储来管理它:
1 | PS C:\> certmgr.msc |
请继续关注后续的技能,来学习现在能对代码签名证书做哪些事!
请注意自签名证书仅在复制到受信任的根证书发布者容器之后才能被信任。当使用 -Trusted
开关参数之后,会自动应用以上操作。