PowerShell 技能连载 - 查看下载文件的大小

当您用 PowerShell 从 internet 下载文件,您可能会想知道下载需要多少时间。您可以检查已下载的数据大小,而且在知道总下载尺寸的情况下可以计算进度百分比。

以下是得到文件尺寸的快速方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Get-DownloadSize
{
[CmdletBinding()]
param
(
[Parameter(Mandatory,ValueFromPipeline)]
[String]
$Url
)

process
{
$webRequest = [System.Net.WebRequest]::Create($Url)
$response = $webRequest.GetResponse()
$response.ContentLength
$response.Dispose()
}
}

以下是一个示例:

1
2
3
PS> "https://github.com/PowerShell/PowerShell/releases/download/v6.2.1/PowerShell-6.2.1-win-x64.zip" | Get-DownloadSize

58716786

PowerShell 技能连载 - 检查按键

有些时候如果一个脚本能检测按键,而不需要干预脚本和输入,那是很棒的事情。通过这种方式,您可以增加按住 SHIFT 键的逻辑,例如退出脚本或者启用详细日志。在用户数据脚本中,您可以在 PowerShell 启动时根据是否按下某些键加载模块以及进行其它调整。

这归结到一个问题:最好的以及最少干预的检测按键的方式是什么?以下是解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName PresentationCore

1..1000 | ForEach-Object {
"I am at $_"
$isDown = [Windows.Input.Keyboard]::IsKeyDown([System.Windows.Input.Key]::LeftShift)
if ($isDown)
{
Write-Warning "ABORTED!!"
break
}

Start-Sleep -Seconds 1
}

增加了两个缺省的程序集之后,您的脚本可以操作 Windows.Input.Keyboard 类。这个类有一个 IsKeyDown() 方法。它可以检测键盘的按键,并且当该按键当前处于按下状态时会返回 $true

以上示例代码持续运行知道用户按下左侧的 SHIFT 键。

PowerShell 技能连载 - 异步使用 FileSystemWatcher

在前一个技能中我们介绍了 FileSystemWatcher 对象以及如何用它监控文件夹的变化。不过,为了不错过所有变化,需要使用异步的方法,类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher.Path = "$home\Desktop"
$FileSystemWatcher.IncludeSubdirectories = $true
$FileSystemWatcher.EnableRaisingEvents = $true
Register-ObjectEvent -InputObject $FileSystemWatcher -SourceIdentifier Monitoring1 -EventName Created -Action {

$Object = "{0} was {1} at {2}" -f $Event.SourceEventArgs.FullPath,
$Event.SourceEventArgs.ChangeType,
$Event.TimeGenerated

Write-Host $Object -ForegroundColor Green
}

在这个例子中,当检测到任何一个变化事件,就会触发一个事件,并且 FileSystemWatcher 继续监听。由 PowerShell 负责响应这个事件,并且它使用一个事件处理器来响应。事件处理器是在后台执行的。

如果不想使用后台事件管理器,请运行这行代码:

1
Get-EventSubscriber -SourceIdentifier Monitoring1 | Unregister-Event

请注意每个支持的变更类型会发出不同的事件。在这个例子中,我们关注 “Created“ 事件,当创建新文件或新文件夹的时候会发出这个事件。要响应其它变化类型,请添加更多的事件处理器。这将返回所有支持的事件名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS C:\> $FileSystemWatcher | Get-Member -MemberType *Event


TypeName: System.IO.FileSystemWatcher

Name MemberType Definition
---- ---------- ----------
Changed Event System.IO.FileSystemEventHandler Changed(System.Object, System.IO.FileSystemEventArgs)
Created Event System.IO.FileSystemEventHandler Created(System.Object, System.IO.FileSystemEventArgs)
Deleted Event System.IO.FileSystemEventHandler Deleted(System.Object, System.IO.FileSystemEventArgs)
Disposed Event System.EventHandler Disposed(System.Object, System.EventArgs)
Error Event System.IO.ErrorEventHandler Error(System.Object, System.IO.ErrorEventArgs)
Renamed Event System.IO.RenamedEventHandler Renamed(System.Object, System.IO.RenamedEventArgs)

PowerShell 技能连载 - 同步使用 FileSystemWatcher

以下是一段演示 PowerShell 如何用 FileSystemWatcher 同步地监控一个包含字文件夹的文件夹变化:

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
$folder = $home
$filter = '*'


try
{
$fsw = New-Object System.IO.FileSystemWatcher $folder, $filter -ErrorAction Stop
}
catch [System.ArgumentException]
{
Write-Warning "Oops: $_"
return
}

$fsw.IncludeSubdirectories = $true
$fsw.NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'

do
{
$result = $fsw.WaitForChanged([System.IO.WatcherChangeTypes]::All, 1000)
if ($result.TimedOut) { continue }

$result
Write-Host "Change in $($result.Name) - $($result.ChangeType)"

} while ($true)

这段代码将会监测用户配置文件中 FileNameLastWrite 属性的变化。

一个同步的监听器会导致 PowerShell 处于忙碌状态,所以要退出监听,需要设置一个 1000ms 的超时值,PowerShell 将会将控制权返回给你,而如果您没有按下 CTRL+C,则循环会继续。

请注意同步的 FileSystemWatcher 可能会错过改变:正好当 WaitForChange() 返回时一个文件发生改变,那么在下一次调用 WaitForChange() 之前的文件变化都会丢失。

如果不想错过任何变化,请使用异步的方法(请见下一个技能)。

PowerShell 技能连载 - 使用代理服务器的缺省凭据

如果您的公司使用一个需要身份认证的代理服务器,PowerShell 可能有时候无法访问 Internet。您可能需要通知代理服务器使用凭据缓存中的缺省凭据:

1
[System.Net.WebRequest]::DefaultWebProxy.Credentials=[System.Net.CredentialCache]::DefaultCredentials

PowerShell 技能连载 - PowerShell 7

今天我们不讨论代码,而是讨论 PowerShell 的总体情况。Microsoft 宣布了下一代 PowerShell 叫做 “PowerShell 7”,并且是基于 .NET Core 3.0 的。这是非常重大的变化,因为 .NET Core 3.0 重新引入了 WPF (Windows Presentation Foundation, GUI),至少在 Windows 平台上。基于这个变化,PowerShell 可以重新引入 GUI 相关的 cmdlet 例如 Out-GridView,并且可以期待 PowerShell 7 能够弥补大多数使用 PowerShell 6 的 Windows 管理员所缺少的功能。

虽然在 PowerShell 7 之前,使用目前的版本也是可行的,但我们建议您关注新闻并阅读以下链接:

https://devblogs.microsoft.com/powershell/the-next-release-of-powershell-powershell-7/

PowerShell 技能连载 - 内置的 RSAT 工具

远程服务器管理工具 (RSAT) 过去是一个外部下载,添加了两个重要的 PowerShell 模块:ActiveDirectoryGroupPolicy。不幸的是,主要的 Windows 更新移除了已安装的 RSAT 工具,所以如果您的脚本需要客户端的 Active Dicrectory 命令,那么需要人工确定并且下载合适新版 Windows 10 的 RSAT 包并且手工安装它。

在 Windows 10 Build 1809 和以后的版本中,这要更容易一些。您可以通过 PowerShell 以类似这样的方式控制 RSAT 状态(假设您有管理员特权):

1
2
3
4
5
6
7
8
9
PS> Get-WindowsCapability -Online -Name *RSAT.ActiveDirectory*


Name : Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0
State : NotPresent
DisplayName : RSAT: Active Directory Domain Services and Lightweight Directory Services Tools
Description : Active Directory Domain Services (AD DS) and Active Directory Lightweight Directory Services (AD LDS) Tools include snap-ins and command-line tools for remotely managing AD DS and AD LDS on Windows Server.
DownloadSize : 5230337
InstallSize : 17043386

要安装 RSAT,请运行以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS> Get-WindowsCapability -Online -Name *RSAT.ActiveDirectory* |
Add-WindowsCapability -Online


Path :
Online : True
RestartNeeded : False




PS> Get-Module -Name ActiveDirectory -ListAvailable


Directory: C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules


ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 1.0.1.0 ActiveDirectory {Add-...

虽然在某些情况下仍然需要下载 RSAT 包,但您不再需要搜索正确的版本并手动构建。

PowerShell 技能连载 - 通过 Index Search 搜索文件

Windows 索引服务能够对您的用户数据文件进行索引,并且在文件资源管理器中快速搜索。以下是一个基于内容返回文件的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Search-FileContent ([String][Parameter(Mandatory)]$FilterText, $Path = $home )
{
$objConnection = New-Object -COM ADODB.Connection
$objRecordset = New-Object -COM ADODB.Recordset

$objConnection.Open("Provider=Search.CollatorDSO;Extended properties='Application=Windows';")

$objRecordset.Open("SELECT System.ItemPathDisplay FROM SYSTEMINDEX WHERE Contains('""$FilterText""') AND SCOPE='$Path'", $objConnection)
While (!$objRecordset.EOF )
{
$objRecordset.Fields.Item("System.ItemPathDisplay").Value
$null = $objRecordset.MoveNext()
}
}

要使用它,请指定一个关键字。以下代码返回所有包含该关键字的文件:

1
2
PS> Search-FileContent -FilterText testcase -Path C:\Users\tobwe\Documents\
C:\Users\tobwe\Documents\Development\experiment1.zip

如你快速发现的,Index Search 并不会返回 PowerShell 脚本(*.ps1 文件)。缺省情况下,PowerShell 脚本并没有被索引。如果您希望通过内容搜索到这些文件,请到 Index Service 设置并且包含 PowerShell 脚本。点击这里了解更多:https://devblogs.microsoft.com/scripting/use-windows-search-to-find-your-powershell-scripts/

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..."