PowerShell 技能连载 - 扩展 Robocopy

PowerShell 可以向原有的命令(例如 robocopy)添加值。请看以下的函数——它用 robocopy 来拷贝文件,并且当拷贝完成时,增加了“扁平拷贝”选项来打开目标文件夹:

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
#requires -Version 3.0

function Copy-FileWithRobocopy
{
param
(
[Parameter(Mandatory)]
[string]$Source,

[Parameter(Mandatory)]
[string]$Destination,

[string]$Filter = '*',

[int]$RetryCount = 0,

[string]$ExcludeDirectory = '',

[switch]$Open,

[switch]$FlatCopy,

[switch]$NoRecurse
)

$Recurse = '/S'
if ($NoRecurse) { $Recurse = '' }

robocopy.exe $Source $Destination $Filter /R:$RetryCount $Recurse /XD $ExcludeDirectory

if ($FlatCopy)
{
Get-ChildItem -Path $Destination -Recurse -Filter $Filter |
Move-Item -Destination $Destination -Force
Get-ChildItem -Path $Destination -Directory |
Remove-Item -Recurse -Force
}

if ($Open)
{
explorer $Destination
}
}

这将会把 Windows 文件夹下所有子文件夹中的 log 文件拷贝到名为 c:\\logs 的新文件夹,并且执行扁平化拷贝:

1
PS>  Copy-FileWithRobocopy -Source $env:windir -Destination c:\logs -Filter *.log -FlatCopy -Open

当您在生产系统使用这段代码之前,请观察 -FlatCopy 是如何工作的:它只是在目标文件夹中查找匹配指定的过滤器,然后将它们移到根目录,最后删除所有文件夹。

所以重复的文件将会被覆盖,而且如果目标文件夹的子文件夹中有其他数据,也会被删除。这是一个很简单的操作,适用于许多情况,但也有很多改进空间。

PowerShell 技能连载 - 复制着色过的代码

当您在 PowerShell ISE 中选中一段代码并复制到剪贴板时,它是以 RTF 格式复制的并且保留了所有颜色代码和字体信息。您可以将它粘贴到支持 RTF 的应用程序,例如 Word 中,就可以看到格式化并着色好的 PowerShell 代码。

要调节字体大小,您不能用 PowerShell ISE 右下角的滑块。这个滑块只是改变 PowerShell ISE 中的字体大小,并不会影响复制的代码字体大小。

正确的方法是,在 PowerShell ISE 中,选择工具/选项,然后在选项对话框中调节字体大小。

PowerShell 技能连载 - 在 PowerShell 中创建 WinForms GUI 界面

虽然建议使用更现代的 WPF 技术来创建 PowerShell 用户界面,但您在某些时候仍然会希望使用更早的 WinForms 技术。特别是要在没有安装 .NET framework 3.51 及以上版本的机器上运行时。WinForms 用户界面需要很多代码,而且不是那么直观。

所以这里提供一个免费的 PowerShell 在线图形设计器,可以快速地创建代码: http://www.poshgui.com/

PowerShell 技能连载 - 使用“Exit”和 Linux 通信

当一个 PowerShell 脚本结束时,您可以使用“Exit”命令来返回一个数值。这在 Windows 世界中是一个很好的实践。它能够设置“Error Level”值,并能够被调用者(例如一个批处理文件或是定时任务管理器)读取到。

1
exit 99

既然 PowerShell 在 Linux 上也可以运行,它也可以用来报告调用 Linux 进程的状态值。

PowerShell 技能连载 - 捕获 Linux 输出

如果您在 Linux 上运行 PowerShell,您可以混合使用 Linux 命令和 PowerShell 命令。要将 Linux 命令的输出赋值给 PowerShell 变量,请像这样写:

1
$content = (ls)

请注意“ls”在 Windows 系统上是一个别名,但在 Linux 系统上指向的是原始的 ls 命令。

PowerShell 技能连载 - 检测文件或文件夹

Test-Path 可以检测一个文件或文件夹是否存在。如果您添加了 -PathType 来指定叶子节点(文件),或 -Container(文件夹),结果会更具体:

$path = 'c:\windows'

Test-Path -Path $path
Test-Path -Path $path -PathType Leaf
Test-Path -Path $path -PathType Container

PowerShell 技能连载 - 系统内存、单位和四舍五入

有些时候,您可能会需要不同的度量单位。例如整个系统的内存是以字节计算的。以下是一些将字节转换为 GB 并且仍然保证可读性的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$memory = Get-WmiObject -Class Win32_ComputerSystem |
Select-Object -ExpandProperty TotalPhysicalMemory

$memoryGB = $memory/1GB

# raw result in bytes
$memoryGB

# rounding
[Int]$memoryGB
[Math]::Round($memoryGB)
[Math]::Round($memoryGB, 1)

# string formatting
'{0:n1} GB' -f $memoryGB

结果看起来类似如下:

15.8744087219238
16
16
15.9
15.9 GB

PowerShell 技能连载 - 创建 Time Span

您可以用 New-TimeSpan 来定义时间的“量”,然后对某个日期增加或减少这个量。以下是一个例子:

1
2
3
4
5
$1Day = New-TimeSpan -Days 1
$today = Get-Date
$yesterday = $today - $1Day

$yesterday

更简单的办法是使用 DateTime 对象的内置方法:

1
2
3
4
$today = Get-Date
$yesterday = $today.AddDays(-1)

$yesterday

您也可以使用 TimeSpan .NET 类来创建 time span 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS C:\> [Timespan]::FromDays(1)


Days : 1
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 0
Ticks : 864000000000
TotalDays : 1
TotalHours : 24
TotalMinutes : 1440
TotalSeconds : 86400
TotalMilliseconds : 86400000

PowerShell 技能连载 - 等待进程退出

有时候,一个 PowerShell 脚本需要等待外部进程结束。以下是一些用户的做法:

1
2
3
4
5
$processNameToWaitForExit = 'notepad'
do
{
Start-Sleep -Seconds 1
} while (Get-Process -Name $processNameToWaitForExit -ErrorAction SilentlyContinue)

这种做法不太理想,因为它至少等待了一秒钟,即便进程已经不在运行了。以下是一个更好的方法:

1
2
$processNameToWaitForExit = 'notepad'
Wait-Process -Name $processNameToWaitForExit -ErrorAction SilentlyContinue

不仅代码更短,Wait-Process 也支持超时时间。如果等待的时间过长,您可以通过超时时间来结束等待。

PowerShell 技能连载 - 对启用 PIN 的用户使用 PowerShell Remoting

如果您设置了 PIN 用来登录您的电脑,对您自己的机器使用 PowerShell remoting 可能会失败,提示如下奇怪的错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS C:\>  Invoke-Command { "Hello" } -ComputerName $env:computername
[DESKTOP-7AAMJLF] Connecting to remote server DESKTOP-7AAMJLF failed with the following error message : WinRM cannot process the request. The following error with errorcode 0x8009030e occurred while using Negotiate authentication: A specified logon session does not exist. It may already have been terminated.
Possible causes are:
-The user name or password specified are invalid.
-Kerberos is used when no authentication method and no user name are specified.
-Kerberos accepts domain user names, but not local user names.
-The Service Principal Name (SPN) for the remote computer name and port does not exist.
-The client and remote computers are in different domains and there is no trust between the two domains.
After checking for the above issues, try the following:
-Check the Event Viewer for events related to authentication.
-Change the authentication method; add the destination computer to the WinRM TrustedHosts configuration setting or use HTTPS transport.
Note that computers in the TrustedHosts list might not be authenticated.
-For more information about WinRM configuration, run the following command: winrm help config. For more information, see the
about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (DESKTOP-7AAMJLF:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : 1312,PSSessionStateBroken

要解决这个问题,您可以有两个选择:

  • 设置一个使用密码的用户账户(需要本地管理员权限)。然后,运行 Invoke-Command 的时候使用 -Credential 参数,然后指定账户和密码。
  • 如果您的电脑没有加入域,那么您需要启用 Negotiate 认证来进行 PowerShell remoting 操作。,并且使用机器的 IP 地址而不是计算机名。