PowerShell 技能连载 - 从网站读取 HTTP 消息头

当您导航到一个网页时,您的浏览器会静默地接收 HTTP 标头中的元信息,而这些元信息通常是不可见的。

要显示任何网站的 HTTP 消息头,请尝试以下操作:

1
2
3
# replace URL with any web page you like:
$url = 'www.tagesschau.de'
(Invoke-WebRequest -Method Head -Uri $url -UseBasicParsing).RawContent

诀窍是使用 -Method 参数并提交 Head 值来获取消息头而不是网页内容。

结果类似于:

HTTP/1.1 200 OK
Connection: keep-alive
Access-Control-Allow-Origin: *
Cache-Control: max-age=14
Content-Type: text/html; charset=utf-8
Date: Tue, 10 Aug 2021 12:06:37 GMT

The header info returned by a web page can vary greatly. When you replace the URL in above example with ‘www.google.de’, for example, you see many more instructions being sent to your browser, and you can actually “see” how the web page instructs your browser to set new cookies – so you can check whether the web page sets cookies before asking for consent or not:
网页返回的标题信息可能会有很大差异。例如,当您将上面示例中的 URL 替换为 “www.google.de" 时,您会看到更多的指令被发送到您的浏览器,您实际上可以“看到”网页如何指示您的浏览器设置新的 cookie——因此您可以在征求同意之前检查网页是否设置了 cookie:

HTTP/1.1 200 OK
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Transfer-Encoding: chunked
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Date: Tue, 10 Aug 2021 12:07:06 GMT
Expires: Tue, 10 Aug 2021 12:07:06 GMT
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Set-Cookie: NID=221=C8RdXG_2bB_MwOG33_lS0hx3P5TF5_vaamoT0xj3yKgZPzCjr_g70NvJXkhenV_GMt1TYYU6XwmNOtlkRKRADiXgYJWWYp671M3DFL8DCxM_J1Cl01r39-jfA7sIxu1C
-0B7CHI-8WfXj5IGZ5dHtRxndNA84cpQov5phLhi7l8; expires=Wed, 09-Feb-2022 12:07:06 GMT; path=/; domain=.google.de; HttpOnly
Server: gws

如果您愿意,您也可以以表格形式获取消息头 —— 只需将 RawContent 替换为 Headers

1
2
3
# replace URL with any web page you like:
$url = 'www.google.de'
(Invoke-WebRequest -Method Head -Uri $url -UseBasicParsing).Headers

PowerShell 技能连载 - 使用 FTP:上传文件(第 4 部分)

PowerShell 不附带用于通过 FTP 下载和上传数据的 cmdlet。但是,您可以使用 .NET 来实现。

要将文件从本地驱动器上传到 FTP 服务器,请尝试以下代码:

1
2
3
4
5
6
7
8
9
$localFile = "C:\test.txt"

$username='testuser'
$password='P@ssw0rd'

[System.Uri]$uri = "ftp://${username}:$password@192.168.1.123/test.txt"
$webclient = [System.Net.WebClient]::new()
$webclient.UploadFile($uri, $localFile)
$webclient.Dispose()

PowerShell 技能连载 - 使用 FTP:下载二进制文件(第 3 部分)

PowerShell 不附带用于通过 FTP 下载和上传数据的 cmdlet。但是,您可以使用 .NET 来实现。

要以二进制模式从 FTP 服务器下载文件,请尝试以下代码,该代码还说明了如何使用显式凭据进行身份验证:

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
$username='testuser'
$password='P@ssw0rd'
[System.Uri]$uri='ftp://192.168.1.123/test.txt'

$localFile = 'C:\test.txt'

$ftprequest=[System.Net.FtpWebRequest]::Create($uri)
$ftprequest.Credentials=[System.Net.NetworkCredential]::new($username,$password)
$ftprequest.Method=[System.Net.WebRequestMethods+Ftp]::DownloadFile
$ftprequest.UseBinary = $true
$ftprequest.KeepAlive = $false
$response=$ftprequest.GetResponse()
$stream=$response.GetResponseStream()

try
{
$targetfile = [System.IO.FileStream]::($localFile,[IO.FileMode]::Create)
[byte[]]$readbuffer = New-Object byte[] 1024

do{
$readlength = $stream.Read($readbuffer,0,1024)
$targetfile.Write($readbuffer,0,$readlength)
}
while ($readlength -gt 0)

$targetfile.Close()
}
catch
{
Write-Warning "Error occurred: $_"
}

PowerShell 技能连载 - 使用 FTP:下载文件(第 2 部分)

PowerShell 不附带用于通过 FTP 下载和上传数据的 cmdlet。但是,您可以为此使用 .NET。

要从 FTP 服务器下载文件,请尝试使用以下代码:

1
2
3
4
5
6
7
8
9
$localFile = "C:\test.txt"

$username='testuser'
$password='P@ssw0rd'

[System.Uri]$uri = "ftp://${username}:$password@192.168.1.123/test.txt"
$webclient = [System.Net.WebClient]::new()
$webclient.DownloadFile($uri, $localFile)
$webclient.Dispose()

PowerShell 技能连载 - 使用 FTP:列出文件夹(第 1 部分)

PowerShell 不附带用于通过 FTP 下载和上传数据的 cmdlet。但是,您可以使用 .NET 来实现。

要显示 FTP 文件夹的内容,请尝试使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$username='testuser'
$password='P@ssw0rd'
$ftp='ftp://192.168.1.123'
$subfolder='/'

[System.Uri]$uri = $ftp + $subfolder
$ftprequest=[System.Net.FtpWebRequest]::Create($uri)
$ftprequest.Credentials= [System.Net.NetworkCredential]::new($username,$password)
$ftprequest.Method=[System.Net.WebRequestMethods+Ftp]::ListDirectory
$response=$ftprequest.GetResponse()
$stream=$response.GetResponseStream()
$reader=[System.IO.StreamReader]::new($stream,[System.Text.Encoding]::UTF8)
$content=$reader.ReadToEnd()

$content

PowerShell 技能连载 - 发现公共 IP 地址

使用 Web 服务,确定您的公共 IP 地址和有关您的 ISP 的信息几乎是举手之劳:

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


ip : 84.165.49.158
hostname : p54a5319e.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 技能连载 - 显示警告对话框(第 2 部分)

在之前的技能中,我们创建了新的快捷方式文件,您已经看到 CreateShortcut() 方法如何提供方法来控制快捷方式的几乎所有细节。这是在桌面上创建 PowerShell 快捷方式的代码:

这个技能可以帮助您始终在其他窗口的顶部显示对话框。为此,下面的代码创建了一个新的虚拟窗口,并确保该窗口位于所有其他窗口的顶部。然后,此窗口用作弹出对话框的父级:

1
2
3
4
5
6
7
8
9
10
11
12
13
Add-Type -AssemblyName  System.Windows.Forms
$message = 'Your system will shutdown soon!'
$title = 'Alert'

# create a dummy window
$form = [System.Windows.Forms.Form]::new()
$form.TopMost = $true

# use the dummy window as parent for the dialog so it appears on top of it
[System.Windows.Forms.MessageBox]::Show($form, $message, $title, [System.Windows.Forms.MessageBoxButtons]::OKCancel, [System.Windows.Forms.MessageBoxIcon]::Warning)

# don't forget to dispose the dummy window after use
$form.Dispose()

PowerShell 技能连载 - 显示警告对话框(第 1 部分)

这是一个显示弹出警告对话框的快速代码示例:

1
2
3
4
5
Add-Type -AssemblyName  System.Windows.Forms
$message = 'Your system will shutdown soon!'
$title = 'Alert'

[System.Windows.Forms.MessageBox]::Show($message, $title, [System.Windows.Forms.MessageBoxButtons]::OKCancel, [System.Windows.Forms.MessageBoxIcon]::Warning)

这些是可用的按钮样式:

1
2
3
4
5
6
7
PS> [Enum]::GetNames([System.Windows.Forms.MessageBoxButtons])
OK
OKCancel
AbortRetryIgnore
YesNoCancel
YesNo
RetryCancel

这些是可用的图标样式:

1
2
3
4
5
6
7
8
9
10
PS> [Enum]::GetNames([System.Windows.Forms.MessageBoxIcon])
None
Hand
Error
Stop
Question
Exclamation
Warning
Asterisk
Information

PowerShell 技能连载 - 处理控制台命令的错误

有时,在 PowerShell 脚本中使用控制台应用程序很有用,甚至是必要的。例如,在上一个技能中,我们研究了删除映射网络驱动器的方法,尽管 Remove-PSDrive 声称能够执行此操作,但最可靠的方法仍然是使用旧的 net.exe 控制台命令。

让我们快速看看如何检查控制台命令是否成功完成。

让我们尝试通过映射一个新的网络驱动器,然后使用控制台应用程序将其删除。当然,您可以将相同的原则应用于您可能需要在 PowerShell 脚本中运行的任何控制台应用程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS> net use z: \\127.0.0.1\c$
The command completed successfully.


PS> net use z: /delete
z: was deleted successfully.

PS> net use z: /delete
net : The network connection could not be found.
At line:1 char:1
+ net use z: /delete
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (The network con...d not be found.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError

More help is available by typing NET HELPMSG 2250.

创建好映射驱动器,然后再删除。但是,状态消息是本地化的(因此很难将它们与多国环境中的预期值进行比较),并且错误会作为异常出现。

通过使用 $?,您可以将输出转换为仅 $true$false$true 表示命令成功完成,而 $false 表示(任何)错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> $null = net use z: \\127.0.0.1\c$; $result = $?; $result
True

PS> $null = net use z: \\127.0.0.1\c$; $result = $?; $result
net : System error 85 has occurred.
At line:1 char:9
+ $null = net use z: \\127.0.0.1\c$; $result = $?; $result
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (System error 85 has occurred.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError

The local device name is already in use.
False

这要好得多,因为您的脚本随后可以判断 $result 并在出现错误时采取适当的步骤或将消息写入日志文件。

但是,如果出现错误,详细的错误信息仍会发送到控制台,并且没有(明显的)方法可以将其删除。即使是成熟的 try...catch 错误处理程序也不会响应它们:

1
2
3
4
5
6
7
8
9
10
PS> $null = try { net use z: \\127.0.0.1\c$} catch {}; $result = $?; $result
net : System error 85 has occurred.
At line:1 char:15
+ $null = try { net use z: \\127.0.0.1\c$} catch {}; $result = $?; $res ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (System error 85 has occurred.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError

The local device name is already in use.
False

原因是:控制台应用程序不会抛出 .NET 异常。发生错误时,控制台应用程序仅通过输出流 #2 发出信息。

而这恰好是包装控制台应用程序的解决方案:将所有流重定向到输出流。这段代码映射了一个网络驱动器:第一次调用成功所有后续调用都失败:

1
2
3
4
5
6
7
8
PS> $null = net use z: \\127.0.0.1\c$ *>&1; $?
True

PS> $null = net use z: \\127.0.0.1\c$ *>&1; $?
False

PS> $null = net use z: \\127.0.0.1\c$ *>&1; $?
False

同样,这会再次删除映射的驱动器,就像在第一次调用成功之前一样,所有剩余的调用都会失败:

1
2
3
4
5
6
7
8
PS> $null = net use z: /delete *>&1; $result = $?; $result
True

PS> $null = net use z: /delete *>&1; $result = $?; $result
False

PS> $null = net use z: /delete *>&1; $result = $?; $result
False

PowerShell 技能连载 - 移除网络驱动器

虽然 Remove-PSDrive 可以删除包括网络驱动器在内的所有类型的驱动器,但更改可能要等系统重新启动时才能生效。这当然是荒谬的。

这个任务是一个很好的例子,为什么 PowerShell 使控制台应用程序成为平等公民是有用的。事实上,即使在 2021 年,移除网络驱动器的最可靠方法也是使用旧的但经过验证的 net.exe。下面的示例删除网络驱动器号 Z:

1
2
PS> net use z: /delete
z: was deleted successfully.