PowerShell 技能连载 - PSCustomObject 到底如何工作

在前一个技能中我们介绍了如何快速用 PSCustomObject 创建一个新对象:

$o = [PSCustomObject]@{
    Date     = Get-Date
    BIOS     = Get-WmiObject -Class Win32_BIOS
    Computer = $env:COMPUTERNAME
    OS       = [Environment]::OSVersion
    Remark   = 'Some remark'
}

实际中,[PSCustomObject] 并不是一个类型,并且也不是在转型一个哈希表。这个场景背后实际发生的是两个步骤的组合,您也可以分别执行这两个步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
#requires -Version 3.0

# create an ordered hash table
$hash = [Ordered]@{
Date = Get-Date
BIOS = Get-WmiObject -Class Win32_BIOS
Computer = $env:COMPUTERNAME
OS = [Environment]::OSVersion
Remark = 'Some remark'
}

# turn hash table in object
$o = New-Object -TypeName PSObject -Property $hash

由于这段代码用到了 PowerShell 3.0 引入的排序的哈希表,所以无法在 PowerShell 2.0 中使用相同的做法。要支持 PowerShell 2.0 ,请用无序的(传统的)哈希表代替:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#requires -Version 2.0

# create a hash table
$hash = @{
Date = Get-Date
BIOS = Get-WmiObject -Class Win32_BIOS
Computer = $env:COMPUTERNAME
OS = [Environment]::OSVersion
Remark = 'Some remark'
}

# turn hash table in object
$o = New-Object -TypeName PSObject -Property $hash

$o

PowerShell 技能连载 - 创建新对象的快速方法

要将一系列信息打包起来的最好方法就是将它们存储在自定义对象中。最简单最快捷的方法就是用 PSCustomObject

1
2
3
4
5
6
7
8
#requires -Version 3.0
$o = [PSCustomObject]@{
Date = Get-Date
BIOS = Get-WmiObject -Class Win32_BIOS
Computer = $env:COMPUTERNAME
OS = [Environment]::OSVersion
Remark = 'Some remark'
}

在大括号内,将一系列信息(或命令执行结果)存储在键中。这将创建一个将包含一系列信息的对象:

PS C:\> $o


Date : 10/28/2016 3:47:27 PM
BIOS : \DESKTOP-7AAMJLF\root\cimv2:Win32_BIOS.Name=”1.4.4”,SoftwareElementID=”1.4.4”,SoftwareElementState=3,TargetOpera
tingSystem=0,Version=”DELL - 1072009”
Computer : DESKTOP-7AAMJLF
OS : Microsoft Windows NT 10.0.14393.0
Remark : Some remark



PS C:> $o.Remark
Some remark

PS C:\> $o.OS

Platform ServicePack Version      VersionString
-------- ----------- -------      -------------
 Win32NT             10.0.14393.0 Microsoft Windows NT 10.0.14393.0


PS C:> $o.OS.VersionString
Microsoft Windows NT 10.0.14393.0

PS C:\>

PowerShell 技能连载 - 利用命令行历史

PowerShell 将您的所有交互命令行输入“记录”到它的命令行历史中,而 Get-History 负责显示它们。如果您运行了一段时间 PowerShell 并且觉得运行的效果不错,那么可以用以下脚本将所有的交互命令从命令行历史中复制到剪贴板中。接下来您可以将它们粘贴到 PowerShell ISE 中,并使之成为一个脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# define how old your commands may be at most to be included
$MaxAgeHours = 4

# get all command history items that were started after this
$DateLimit = (Get-Date).AddHours(-$MaxAgeHours)


# get all command-line commands
Get-History |
# exclude all that were aborted
Where-Object ExecutionStatus -eq Completed |
# exclude all that are older than the limit set above
Where-Object StartExecutionTime -gt $DateLimit |
# get just the command-line
Select-Object -ExpandProperty CommandLine |
# copy all to clipboard
clip.exe

PowerShell 技能连载 - 获取PowerShell Gallery 模块的最新版本

www.powershellgallery.com ,Microsoft 发布了一个公开的脚本和模块的仓库。您可以在这里和其他人交流 PowerShell 代码(请见网站)。

要使用这个仓库,您需要 PowerShell 5 或者手动安装 PowerShellGet 模块(在 powershellgallery.com 可以下载到)。接下来您可以使用诸如 Find/Save/Install/Update/Remove-Script/Module 等 cmdlet。

目前缺乏的是一个查看模块当前最新版本号的方法。以下是解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Get-PublishedModuleVersion($Name)
{
# access the main module page, and add a random number to trick proxies
$url = "https://www.powershellgallery.com/packages/$Name/?dummy=$(Get-Random)"
$request = [System.Net.WebRequest]::Create($url)
# do not allow to redirect. The result is a "MovedPermanently"
$request.AllowAutoRedirect=$false
try
{
# send the request
$response = $request.GetResponse()
# get back the URL of the true destination page, and split off the version
$response.GetResponseHeader("Location").Split("/")[-1] -as [Version]
# make sure to clean up
$response.Close()
$response.Dispose()
}
catch
{
Write-Warning $_.Exception.Message
}
}

Get-PublishedModuleVersion -Name ISESteroids

当您运行 Get-PublishedModuleVersion 并传入发布的模块名,执行结果类似如下:

PS C:\> Get-PublishedModuleVersion -Name ISESteroids

Major  Minor  Build  Revision
-----  -----  -----  --------
2      6      3      25

这个操作非常非常快,而且可以用来检测已安装的模块,看它们是否是最新版本。

PowerShell 技能连载 - (分别)测试文件和文件夹

Test-Path 在测试一个文件或文件夹是否存在时十分有用,它可以用在任何 PowerShell 驱动器上。所以它也可以测试一个变量、一个函数,或一个证书是否存在(举个例子)。

In recent PowerShell versions, Test-Path can now differentiate between containers (i.e. folders) and leafs (i.e. files), too:

在近期的 PowerShell 版本中,Test-Path 还可以区分容器(例如文件夹)和叶子(例如文件):

1
2
3
4
5
6
7
$path = 'c:\windows\explorer.exe'
# any item type
Test-Path -Path $path
# just files
Test-Path -Path $path -PathType Leaf
# just folders
Test-Path -Path $path -PathType Container

PowerShell 技能连载 - 获取 Cmdlet 参数的帮助

在 PowerShell 5.0 中似乎有个 bug,限制了内置帮助窗口的作用。当您以 -ShowWindow 参数运行 Get-Help 命令时,该窗口只显示该 cmdlet 的语法和例子。许多额外信息并没有显示出来。

要获得某个 cmdlet 支持的参数的详细信息,请直接请求该信息。以下代码将解释 Get-Date 中的 -Format 参数是做什么的:

PS C:\> Get-Help -Name Get-Date -Parameter Format

-Format []
    Displays the date and time in the Microsoft .NET Framework format indicated by the
    format specifier. Enter a format specifier. For a list of available format
    specifiers, see DateTimeFormatInfo Class
    (http://msdn.microsoft.com/library/system.globalization.datetimeformatinfo.aspx)
    in MSDN.

    When you use the Format parameter, Windows PowerShell gets only the properties of
    the DateTime object that it needs to display the date in the format that you
    specify. As a result, some of the properties and methods of DateTime objects might
    not be available.

    Starting in Windows PowerShell 5.0, you can use the following additional formats
    as values for the Format parameter.

    -- FileDate. A file or path-friendly representation of the current date in local
    time. It is in the form of yyyymmdd ( using 4 digits, 2 digits, and 2 digits). An
    example of results when you use this format is 20150302.

    -- FileDateUniversal. A file or path-friendly representation of the current date
    in universal time. It is in the form of yyyymmdd + 'Z' (using 4 digits, 2 digits,
    and 2 digits). An example of results when you use this format is 20150302Z.

    -- FileDateTime. A file or path-friendly representation of the current date and
    time in local time, in 24-hour format. It is in the form of yyyymmdd + 'T' +
    hhmmssmsms, where msms is a four-character representation of milliseconds. An
    example of results when you use this format is 20150302T1240514987.

    -- FileDateTimeUniversal. A file or path-friendly representation of the current
    date and time in universal time, in 24-hour format. It is in the form of yyyymmdd
    + 'T' + hhmmssmsms, where msms is a four-character representation of milliseconds,
    + 'Z'. An example of results when you use this format is 20150302T0840539947Z.

    Required?                    false
    Position?                    named
    Default value                none
    Accept pipeline input?       false
    Accept wildcard characters?  false

通过这些信息,您现在可以知道如何格式化日期和时间:

1
2
3
$date = Read-Host -Prompt 'Enter a date'
$weekday = Get-Date -Date $date -Format 'dddd'
"$date is a $weekday"

PowerShell 技能连载 - 用一行代码更新 PowerShell 帮助信息

要获得 PowerShell 最全的输出信息,您需要更新 PowerShell 的帮助至少一次。这将下载并安装当您通过 cmdlet 运行 Get-Help 或是在 PowerShell ISE 中点击一个 cmdlet 并按 F1 时将出现的基础帮助文件集。

更新 PowerShell 帮助需要 Administrator 特权,因为帮助文件存在 Windows 文件夹中。

以下是一个单行命令,演示了如何以管理员特权运行任何 PowerShell 命令。这个命令将更新本地的 PowerShell 帮助文件:

1
Start-Process -FilePath powershell -Verb RunAs -ArgumentList "-noprofile -command Update-Help -UICulture en-us  -Force"

PowerShell 技能连载 - 在 PowerShell ISE 中获得 Cmdlet 的 IntelliSense

如果您在阅读某些加载到 PowerShell ISE 中的 PowerShell 代码,要获取额外信息十分容易。只需要点击想获得详情的 cmdlet,然后按下 CTRL+SPACE 键即可(译者注:可能会和 IME 切换快捷键冲突)。

这将调出 IntelliSense 菜单,即平时按下 “-“ 和 “.” 触发键时显示的菜单。由于只有一个 cmdlet 匹配成功,所以该列表只有一个项目。过一小会儿,就会弹出一个显示该 cmdlet 支持的所有参数的 tooltip。

如果您想了解更多,请按 F1 键。PowerShell ISE 将把当前位置的 cmdlet 名送给 Get-Help 命令。该帮助将在一个独立的新窗口中显示。

PowerShell 技能连载 - 从网站上下载图片

有许多有趣的网站,其中一个是 www.metabene.de (至少面向德国访访客),有 33 页内容,艺术家展示了他的绘画,并提供免费下载(只允许私人使用·私人使用)。

在类似这种情况中,PowerShell 可以帮助您将手动从网站下载图片的操作自动化。在 PowerShell 3.0 中,引入了一个称为 Invoke-WebRequest 的“PowerShell 浏览器”,它能够将人类在一个真实浏览器中操作的大多数事情自动化。

当您运行这段脚本时,它访问所有的 33 个网页,检查所有的图片链接,并将它们保存到硬盘上:

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
# open destination folder (and create it if needed)
$folder = 'c:\drawings'
$exists = Test-Path -Path $folder
if (!$exists) { $null = New-Item -Path $folder -ItemType Directory }
explorer $folder

# walk all 33 web pages that www.metabene.de offers
1..33 | ForEach-Object {
$url = "http://www.metabene.de/galerie/page/$_"

# navigate to website...
$webpage = Invoke-WebRequest -Uri $url -UseBasicParsing

# take sources of all images on this website...
$webpage.Images.src |
Where-Object {
# take only images that were uploaded to this blog
$_ -like '*/uploads/*'
}
} |
ForEach-Object {
# get filename of URL
$filename = $_.Split('/')[-1]
# create local file name
$destination= Join-Path -Path $Folder -ChildPath $filename
# download pictures
Invoke-WebRequest -Uri $url -OutFile $destination
}

PowerShell 技能连载 - Cmelet 错误报告的简单策略

在 PowerShell 中,您可以创建复杂的错误处理代码,但有些时候您可能只是想知道出了什么错并且把它记录下来。不需要额外的技能。

以下是两个可能会遇到错误的 cmdlet。当这些 cmdlet 执行完成时,您将能通过 $data1$data2 获得它们的执行结果,并在控制台中见到许多红色的错误信息:

1
2
$data1 = Get-ChildItem -Path c:\windows -Filter *.ps1 -Recurse
$data2 = Get-Process -FileVersionInfo

现在看看这个实验:

1
2
$data1 = Get-ChildItem -Path c:\windows -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue -ErrorVariable errorList
$data2 = Get-Process -FileVersionInfo -ErrorAction SilentlyContinue -ErrorVariable +errorList

要禁止错误输出并同时将红色的错误信息写入自定义的错误变量 $errorList 并不需要做太多工作。请注意 -ErrorVariable 参数接受的是一个变量名(不含 “$“ 前缀)。并请注意在这个变量名前添加 “+“ 前缀将能把错误信息附加到变量中,而不是替换变量的值。

现在,两个 cmdlet 运行起来都看不到错误信息了。最后,您可以容易地在 $errorList 中容易地分析错误信息,例如用 Out-File 将它们写入某些错误日志文件:

1
2
3
$issues = $errorList.CategoryInfo | Select-Object -Property Category, TargetName
$issues
$issues | Out-File -FilePath $home\Desktop\report.txt -Append