PowerShell 技能连载 - 还原短网址

类似 “http://bit.ly/e0Mw9w" 这样的网址十分短小,并且使用起来很方便。但它们往往屏蔽了原始的信息。

PowerShell 查找它们真实指向的地址,来还原短网址:

1
2
3
4
5
6
7
$url = "http://bit.ly/e0Mw9w"

$request = [System.Net.WebRequest]::Create($url)
$request.AllowAutoRedirect=$false
$response=$request.GetResponse()
$trueUrl = $response.GetResponseHeader("Location")
"$url -> $trueUrl"

以下是使用结果:

http://bit.ly/e0Mw9w -> http://www.leeholmes.com/projects/ps_html5/Invoke-PSHtml5.ps1

PowerShell 技能连载 - 自动壁纸下载器

您对桌面壁纸感到厌倦了吗?PowerShell 可以帮您下载最新的壁纸!以下是一个函数:

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
function Download-Wallpaper
{
param
(
[string]
[Parameter(Mandatory)]
$Folder,

[Parameter(ValueFromPipeline)]
[Int]
$Page=1
)

begin
{
$url = "http://wallpaperswide.com/page/$Page"
$targetExists = Test-Path -Path $Folder
if (!$targetExists) { $null = New-Item -Path $Folder -ItemType Directory }
}
process
{
$web = Invoke-WebRequest -Uri $url -UseBasicParsing

$web.Images.src |
ForEach-Object {

$filename = $_.Split('/')[-1].Replace('t1.jpg','wallpaper-5120x3200.jpg')
$source = "http://wallpaperswide.com/download/$filename"

$TargetPath = Join-Path -Path $folder -ChildPath $filename

Invoke-WebRequest -Uri $source -OutFile $TargetPath
}
}
end
{
explorer $Folder
}
}

以下是使用方法:

1
PS> Download-Wallpaper -Folder c:\wallpapers

它将从一个公开的壁纸网站下载所有壁纸到本地文件夹,然后打开该文件夹。您所需要做的只是右键单击壁纸并且选择“设为桌面背景”。

默认情况下,Download-Wallpaper 从第一个页面下载壁纸。通过指定 -Page 参数,您可以从其它页面挖掘壁纸。请试试以下代码:

1
PS> Download-Wallpaper -Folder c:\wallpapers

PowerShell 技能连载 - 将值保存到 Excel 工作表中

有些时候,您可能会需要更新一个 Excel 工作表中的值。PowerShell 可以操作 Excel 对象模型,不过它很慢。以下是一个打开 Excel 文件,然后写入信息到 A1 单元格,最后保存更改的例子,

请确保您调整了路径,指向一个实际存在的 Excel 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$excel = New-Object -ComObject Excel.Application
# open Excel file
$workbook = $excel.Workbooks.Open("c:\test\excelfile.xlsx")

# uncomment next line to make Excel visible
#$excel.Visible = $true

$sheet = $workbook.ActiveSheet
$column = 1
$row = 1
# change content of Excel cell
$sheet.cells.Item($column,$row) = Get-Random
# save changes
$workbook.Save()
$excel.Quit()

PowerShell 技能连载 - 读取 Excel 单元格

有些时候,您可能会需要从 Excel 工作表中读取信息。PowerShell 可以操作 Microsoft Excel 对象模型,虽然它的速度很慢。

以下是一段延时如何操作 Excel 单元格的示例代码。请确保您调整了以下代码中的路径,指向一个实际存在的 Excel 文件。该代码将读取 A1 单元格的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$excel = New-Object -ComObject Excel.Application
# open Excel file
$workbook = $excel.Workbooks.Open("c:\test\excelfile.xlsx")

# uncomment next line to make Excel visible
#$excel.Visible = $true

$sheet = $workbook.ActiveSheet
$column = 1
$row = 1
$info = $sheet.cells.Item($column, $row).Text
$excel.Quit()


"Cell A1 contained '$info'"

PowerShell 技能连载 - 永久性设置环境变量

PowerShell 只能在它的进程空间里设置环境变量,所以这些改变无法保存,并且在 PowerShell 之外不可见。

要永久性地设置环境变量,可以编写一个简单的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Set-EnvironmentVariable
{
param
(
[string]
[Parameter(Mandatory)]
$Name,

[string]
[AllowEmptyString()]
[Parameter(Mandatory)]
$Value,

[System.EnvironmentVariableTarget]
[Parameter(Mandatory)]
$Target
)

[Environment]::SetEnvironmentVariable($Name, $Value, $Target)
}

现在您可以这样设置环境变量:

1
PS> Set-EnvironmentVariable -Name test -Value 123 -Target User

您也可以传入空字符串来移除一个环境变量。

1
PS> Set-EnvironmentVariable -Name test -Value "" -Target User

这是为什么 -Value 参数定义加上 [AllowEmptyString()] 属性的原因。如果没有这个属性,一个必选参数不能接受一个空字符串,那么该函数就无法移除环境变量。

另一个值得注意的地方是 -Target 参数的类型定义:因为制定了一个枚举类型,所以当您在 PowerShell ISE 或其它带有智能提示的编辑器中使用这个函数时,该编辑器将会贴心地提供智能提示选择。

PowerShell 技能连载 - Select-Object 和 -ExcludeProperty

以下是一行常常迷惑 PowerShell 用户的代码:

1
Get-Service | Select-Object -ExcludeProperty Name

当您使用 Select-Object 时,它的 -ExcludeProperty 参数并没有做任何事情。实际上,ExcludeProperty 只在使用 -Property 的时候才有效。所以这行代码是可以用的:

1
Get-Service | Select-Object -ExcludeProperty Name -Property Status, DisplayName, Name

然而,这看起来很荒谬:为什么通过 -Property 指定了属性,又还要用 ExcludeProperty 来排除它们呢?这样不是更简单吗:

1
Get-Service | Select-Object -Property Status, DisplayName

实际上,-ExcludeProperty 只在使用通配符的时候有意义:

1
2
3
4
5
6
7
8
PS> Get-CimInstance -ClassName Win32_BIOS | Select-Object -Property *BIOS* -ExcludeProperty *major*, *minor*


PrimaryBIOS : True
BiosCharacteristics : {7, 9, 11, 12...}
BIOSVersion : {DELL - 1072009, 1.6.1, American Megatrends - 5000B}
SMBIOSBIOSVersion : 1.6.1
SMBIOSPresent : True

PowerShell 技能连载 - 强制关闭所有 PowerShell ISE 文档

以下是一段强制关闭 PowerShell ISE 中所有打开的文档的代码片段。请注意:它不经提示就关闭所有的文档。它适用于当您搞砸了,并且不准备保存脚本的情况:

1
2
3
4
5
6
7
8
foreach ($tab in $psise.PowerShellTabs)
{
$files = $tab.Files
foreach ($file in $files)
{
$files.Remove($file, $true)
}
}

不过,当您运行这段代码时,您会收到一个错误。即便您不使用 PowerShell ISE,这个错误(和它的修复信息)对您来说可能十分有趣。

这段代码枚举出所有打开的文件并且尝试逐个关闭它们。这并不能工作:当您枚举一个数组时,您无法改变这个数组。所以当 PowerShell 关闭一个文档时,这个文件列表就变化了,而这将打断这个循环。

当发生这个错误时,一个简单的办法是先将这个数组拷贝到另一个数组。然后就可以安全地枚举这个副本数组。拷贝一个数组十分简单,只需要将它强制类型转换为 [Object[]]

以下是正确的代码:

1
2
3
4
5
6
7
8
foreach ($tab in $psise.PowerShellTabs)
{
$files = $tab.Files
foreach ($file in [Object[]]$files)
{
$files.Remove($file, $true)
}
}

PowerShell 技能连载 - 创建快速的 Ping(第六部分)

这是我们迷你系列的最后一部分,向我们超快的 Test-OnlineFast 函数添加管道功能。您现在可以像这样将计算机名通过管道传给函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PS> 1..200 | ForEach-Object { "192.168.189.$_" } | Test-OnlineFast

Address Online DNSName Status
------- ------ ------- ------
192.168.189.200 True DESKTOP-7AAMJLF.fritz.box Success
192.168.189.1 True fritz.box Success
192.168.189.65 True mbecker-netbook.fritz.box Success
192.168.189.29 True fritz.repeater Success
192.168.189.64 True android-6868316cec604d25.fritz.box Success
192.168.189.112 True Galaxy-S8.fritz.box Success
192.168.189.142 True Galaxy-S8.fritz.box Success
192.168.189.129 True iPhonevMuzaffer.fritz.box Success
192.168.189.10 False Request Timed Out
192.168.189.100 False Request Timed Out
(...)

当然,您也可以传递普通参数给这个函数:

1
2
3
4
5
6
7
PS> Test-OnlineFast -ComputerName google.de, microsoft.com, 127.0.0.1

Address Online DNSName Status
------- ------ ------- ------
127.0.0.1 True DESKTOP-7AAMJLF Success
google.de True google.de Success
microsoft.com False Request Timed Out

您甚至可以使用其它 cmdlet 的结果,假设您选择了希望传给该函数的属性。一下这行代码 ping 您 Active Directory 中的所有计算机(您最好稍微做一下限制,以免耗尽资源):

1
PS> Get-ADComputer -Filter * | Select-Object -ExpandProperty DnsHostName | Test-OnlineFast

PowerShell 技能连载 - 创建快速的 Ping(第五部分)

在前一个技能中我们创建了一个名为 Test-OnlineFast 的高速的新的 PowerShell 函数,它使用 WMI 来高速 ping 任意数量的计算机。今天我们将通过向 ping 的结果增加一些列额外的属性使它变得更有用。

先让我们检查 Test-OnlineFast 是如何工作的。以下是一些例子。我们先 ping 一系列计算机。您既可以使用计算机名也可以使用 IP 地址:

1
2
3
4
5
6
7
8
PS> Test-OnlineFast -ComputerName google.de, powershellmagazine.com, 10.10.10.200, 127.0.0.1

Address Online DNSName Status
------- ------ ------- ------
127.0.0.1 True DESKTOP-7AAMJLF Success
google.de True google.de Success
powershellmagazine.com True powershellmagazine.com Success
10.10.10.200 False Request Timed Out

我们现在 ping 整个 IP 地址段。以下例子是从我们的公共酒店 WLAN 中执行的(请将 IP 范围调整为您所在的网络):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS> $iprange = 1..200 | ForEach-Object { "192.168.189.$_" }

PS> Test-OnlineFast -ComputerName $iprange

Address Online DNSName Status
------- ------ ------- ------
192.168.189.200 True DESKTOP-7AAMJLF.fritz.box Success
192.168.189.1 True fritz.box Success
192.168.189.134 True PCSUP03.fritz.box Success
192.168.189.29 True fritz.repeater Success
192.168.189.64 True android-6868316cec604d25.fritz.box Success
192.168.189.142 True Galaxy-S8.fritz.box Success
192.168.189.65 True mbecker-netbook.fritz.box Success
192.168.189.30 True android-7f35f4eadd9e425e.fritz.box Success
192.168.189.10 False Request Timed Out
192.168.189.100 False Request Timed Out
192.168.189.101 False Request Timed Out
(...)

神奇的是它的超快速度。ping 整个子网只用了几秒。

现在,我们来看看这个函数。在前面的技能中我们解释了其中的一部分。这个版本向 ping 的结果增加了有用的属性,例如 OnlineDnsName,它返回关于 ping 状态的友好文本,而不是幻数。所有这些是通过计算属性的哈希表实现的,基于 ping 返回的原始信息:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
function Test-OnlineFast
{
param
(
# make parameter pipeline-aware
[Parameter(Mandatory)]
[string[]]
$ComputerName,

$TimeoutMillisec = 1000
)

# hash table with error code to text translation
$StatusCode_ReturnValue =
@{
0='Success'
11001='Buffer Too Small'
11002='Destination Net Unreachable'
11003='Destination Host Unreachable'
11004='Destination Protocol Unreachable'
11005='Destination Port Unreachable'
11006='No Resources'
11007='Bad Option'
11008='Hardware Error'
11009='Packet Too Big'
11010='Request Timed Out'
11011='Bad Request'
11012='Bad Route'
11013='TimeToLive Expired Transit'
11014='TimeToLive Expired Reassembly'
11015='Parameter Problem'
11016='Source Quench'
11017='Option Too Big'
11018='Bad Destination'
11032='Negotiating IPSEC'
11050='General Failure'
}

# hash table with calculated property that translates
# numeric return value into friendly text

$statusFriendlyText = @{
# name of column
Name = 'Status'
# code to calculate content of column
Expression = {
# take status code and use it as index into
# the hash table with friendly names
# make sure the key is of same data type (int)
$StatusCode_ReturnValue[([int]$_.StatusCode)]
}
}

# calculated property that returns $true when status -eq 0
$IsOnline = @{
Name = 'Online'
Expression = { $_.StatusCode -eq 0 }
}

# do DNS resolution when system responds to ping
$DNSName = @{
Name = 'DNSName'
Expression = { if ($_.StatusCode -eq 0) {
if ($_.Address -like '*.*.*.*')
{ [Net.DNS]::GetHostByAddress($_.Address).HostName }
else
{ [Net.DNS]::GetHostByName($_.Address).HostName }
}
}
}

# convert list of computers into a WMI query string
$query = $ComputerName -join "' or Address='"

Get-WmiObject -Class Win32_PingStatus -Filter "(Address='$query') and timeout=$TimeoutMillisec" |
Select-Object -Property Address, $IsOnline, $DNSName, $statusFriendlyText

}

PowerShell 技能连载 - 创建快速的 Ping(第四部分)

在前一个技能中我们演示了如何用 WMI 快速 ping 多台计算机。那么今天我们将它封装为一个可复用的 PowerShell 函数。它可以快速地 ping 一台或多台计算机。

以下是函数代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Test-OnlineFast
{
param
(
[Parameter(Mandatory)]
[string[]]
$ComputerName,

$TimeoutMillisec = 1000
)

# convert list of computers into a WMI query string
$query = $ComputerName -join "' or Address='"

Get-WmiObject -Class Win32_PingStatus -Filter "(Address='$query') and timeout=$TimeoutMillisec" | Select-Object -Property Address, StatusCode
}

现在要以指定的超时值 ping 多台计算机变得非常简单:

1
2
3
4
5
6
PS> Test-OnlineFast -ComputerName microsoft.com, google.de

Address StatusCode
------- ----------
google.de 0
microsoft.com 11010

状态码 “0” 代表响应结果:主机在线。其他状态码代表失败。

默认情况下,Test-OnlineFast 的超时时间为 1000 毫秒,所以当一台计算机没有响应时,最多等待 1 秒。您可以通过 -TimeoutMillseconds 参数改变超时值。设置越长的超时值意味着命令的执行时间越长。所以您应该在系统足够响应的范围内使用尽可能短的超时时间。

另一个影响时间的变量是 DNS 解析:如果 DNS 解析速度慢,或者无法解析到名称,将增加总体时间。如果指定 IP 地址,就不会发生这种变慢现象。

以下是在几秒内 ping 200 个 IP 地址的例子:

1
2
3
4
5
6
7
8
9
10
11
12
PS> $ComputerName = 1..255 | ForEach-Object { "10.62.13.$_" }

PS> Test-OnlineFast -ComputerName $ComputerName

Address StatusCode
------- ----------
10.62.13.1 11010
10.62.13.10 0
10.62.13.100 0
10.62.13.101 11010
10.62.13.102 11010
(...)