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.

PowerShell 技能连载 - 列出网络驱动器

有很多方法可以列出映射的网络驱动器。其中之一使用 PowerShell 的 Get-PSDrive 并检查目标根目录是否以 “\“ 开头,表示 UNC 网络路径:

1
2
3
4
5
PS> Get-PSDrive -PSProvider FileSystem | Where-Object DisplayRoot -like '\\*'

Name Used (GB) Free (GB) Provider Root CurrentLocation
---- --------- --------- -------- ---- ---------------
Z 11076,55 0,00 FileSystem \\192.168.2.107\docs

一个不错的方面是 Get-PSDrive 返回有用的附加详细信息,例如驱动器的大小。

PowerShell 技术 QQ 群