PowerShell 技能连载 - 自动生成文档和报告(第 2 部分)

Iain Brighton 创建了一个名为 “PScribo” 的免费的 PowerShell 模块,可以快速地创建文本、HTML 或 Word 格式的文档和报告。

要使用这个模块,只需要运行这条命令:

1
Install-Module -Name PScribo -Scope CurrentUser -Force

今天,我们将生成一个包含动态表格内容的文档:

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
# https://github.com/iainbrighton/PScribo
# help about_document

# create a folder to store generated documents
$OutPath = "c:\temp\out"
$exists = Test-Path -Path $OutPath
if (!$exists) { $null = New-Item -Path $OutPath -ItemType Directory -Force }

# generate document
Document 'ServiceReport' {
# generate the service information to use
# (requires PowerShell 5 because prior to PowerShell 5, Get-Service does not supply
# StartType - alternative: use Get-WmiObject -Class Win32_Service, and adjust
# property names)
$services = Get-Service | Select-Object -Property DisplayName, Status, StartType

Paragraph -Style Heading1 "System Inventory for $env:computername"
Paragraph -Style Heading2 "Services ($($services.Count) Services found):"

# generate a table with one line per service
$services |
# select the properties to display, and the header texts to use
Table -Columns DisplayName, Status, StartType -Headers 'Service Name','Current State','Startup Type' -Width 0

} |
Export-Document -Path $OutPath -Format Word,Html,Text

# open the generated documents
explorer $OutPath

当您运行这段代码时,它生成三个名为 ServiceReport.docx/html/txt 的文件。如您所见,该报告包含表格形式的服务列表。

PowerShell 技能连载 - 自动生成文档和报告(第 1 部分)

Iain Brighton 创建了一个名为 “PScribo” 的免费的 PowerShell 模块,可以快速地创建文本、HTML 或 Word 格式的文档和报告。

要使用这个模块,只需要运行这条命令:

1
Install-Module -Name PScribo -Scope CurrentUser -Force

下一步,您可以类似这样生成简单的文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# https://github.com/iainbrighton/PScribo
# help about_document

# create a folder to store generated documents
$OutPath = "c:\temp\out"
$exists = Test-Path -Path $OutPath
if (!$exists) { $null = New-Item -Path $OutPath -ItemType Directory -Force }


Document 'Report' {
Paragraph -Style Heading1 "System Inventory for $env:computername"
Paragraph -Style Heading2 'BIOS Information'
Paragraph 'BIOS details:' -Bold
$bios = Get-WmiObject -Class Win32_BIOS | Out-String
Paragraph $bios.Trim()
} |
Export-Document -Path $OutPath -Format Word,Html,Text

# open the generated documents
explorer $OutPath

PowerShell 技能连载 - 从函数中返回富对象(第 2 部分)

当一个函数返回多于四个属性时,PowerShell 将输出结果格式化为列表,否则格式化为表格。在您学习新的方法来影响这种行为之前,请自己验证一下。以下函数返回一个多于 6 个属性的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Get-TestData
{
# if a function is to return more than one information kind,
# wrap it in a custom object

[PSCustomObject]@{
# wrap anything you'd like to return
ID = 1
Random = Get-Random
Date = Get-Date
Text = 'Hello'
BIOS = Get-WmiObject -Class Win32_BIOS
User = $env:username
}
}

结果是以表格形式呈现:

1
2
3
4
5
6
7
8
9
10
PS> Get-TestData


ID : 1
Random : 147704985
Date : 25.05.2018 13:09:26
Text : Hello
BIOS : \\DESKTOP-7AAMJLF\root\cimv2:Win32_BIOS.Name="1.6.1",SoftwareElementID="1.6.1",SoftwareElementState=3,TargetOperatingSys
tem=0,Version="DELL - 1072009"
User : tobwe

当移除掉一些属性,限制属性个数为 4 个或更少时,PowerShell 输出一个表格:

1
2
3
4
5
PS> Get-TestData

ID Random Text User
-- ------ ---- ----
1 567248729 Hello tobwe

通常,表格的形式比较容易阅读,特别是有多个数据集的时候。当您得到一个 4 个或更少属性的表格时,您可能不是始终希望返回值只有 4 个属性。所以为什么不像类似 cmdlet 一样处理它呢?

Cmdlet 默认情况下只显示属性的一部分:

1
2
3
4
5
PS> Get-Service | Select-Object -First 1

Status Name DisplayName
------ ---- -----------
Running AdobeARMservice Adobe Acrobat Update Service

使用 Select-Object 可以显示地获得所有属性的列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PS> Get-Service | Select-Object -First 1 -Property *


Name : AdobeARMservice
RequiredServices : {}
CanPauseAndContinue : False
CanShutdown : False
CanStop : True
DisplayName : Adobe Acrobat Update Service
DependentServices : {}
MachineName : .
ServiceName : AdobeARMservice
ServicesDependedOn : {}
ServiceHandle :
Status : Running
ServiceType : Win32OwnProcess
StartType : Automatic
Site :
Container

显然,有第一公民和第二公民之分。在您自己的函数中,您可以类似这样定义第一公民:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Get-TestData
{
# define the first-class citizen
[string[]]$visible = 'ID','Date','User'
$info = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',$visible)


[PSCustomObject]@{
# wrap anything you'd like to return
ID = 1
Random = Get-Random
Date = Get-Date
Text = 'Hello'
BIOS = Get-WmiObject -Class Win32_BIOS
User = $env:username
} |
# add the first-class citizen info to your object
Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $info -PassThru

}

现在,您的函数的行为类似 cmdlet,而且您没有定义多于 4 个一等公民,所以缺省情况下得到一个表格的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS> Get-TestData

ID Date User
-- ---- ----
1 25.05.2018 13:15:15 tobwe



PS> Get-TestData | Select-Object -Property *


ID : 1
Random : 1298877814
Date : 25.05.2018 13:15:22
Text : Hello
BIOS : \\DESKTOP-7AAMJLF\root\cimv2:Win32_BIOS.Name="1.6.1",SoftwareElementID="1.6.1",SoftwareElementState=3,TargetOperatingSys
tem=0,Version="DELL - 1072009"
User : tobwe

PowerShell 技能连载 - 从函数中返回富对象(第 1 部分)

如果一个 PowerShell 函数需要返回多于一类信息,请确保将这些信息集中到一个富对象中。最简单的实现方式是创建一个类似 [PSCustomObject]@{} 这样的自定义对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Get-TestData
{
# if a function is to return more than one information kind,
# wrap it in a custom object

[PSCustomObject]@{
# wrap anything you'd like to return
ID = 1
Random = Get-Random
Date = Get-Date
Text = 'Hallo'
BIOS = Get-WmiObject -Class Win32_BIOS
User = $env:username
}
}

自定义对象的核心是一个哈希表:每个哈希表键将会转换为一个属性。这个方式的好处是您可以使用哈希表中的变量甚至命令,所以这样要收集您想返回的所有信息,将它合并为一个自描述的对象很容易:

1
2
3
4
5
6
7
8
9
10
PS> Get-TestData


ID : 1
Random : 1794057589
Date : 25.05.2018 13:06:57
Text : Hallo
BIOS : \\DESKTOP-7AAMJLF\root\cimv2:Win32_BIOS.Name="1.6.1",SoftwareElementID="1.6.1",SoftwareElementState=3,TargetOperatingSys
tem=0,Version="DELL - 1072009"
User : tobwe

PowerShell 技能连载 - 在函数内使用持久变量

默认情况下,当一个 PowerShell 函数退出时,它将“忘记”所有的内部变量。然而,有一种办法可以创建持久的内部变量。以下是实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# create a script block with internal variables
# that will persist
$c = & {
# define an internal variable that will
# PERSIST and keep its value even though
# the function exits
$a = 0

{
# use the internal variable
$script:a++
"You called me $a times!"
}.GetNewClosure()
}

这段代码创建一个包含内部变量的脚本块。当您多次运行这个脚本块时,计数器会累加:

1
2
3
4
5
6
7
8
PS> & $c
You called me 1 times!

PS> & $c
You called me 2 times!

PS> & $c
You called me 3 times!

然而,脚本内的 $a 变量的作用域既不是 global 也不是 scriptglobal。它的作用域只在脚本块的内部:

1
PS> $a

要将脚本块转换为函数,请加上这段代码:

1
2
3
4
5
6
7
PS> Set-Item -Path function:Test-Function -Value $c

PS> Test-Function
You called me 5 times!

PS> Test-Function
You called me 6 times!

PowerShell 技能连载 - 使用缺省参数

If you find yourself always using the same parameter values over again, try using PowerShell default parameters. Here is how:
如果您常常反复使用相同的参数值,试着使用 PowerShell 的缺省参数。以下是实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
# hash table
# Key =
# Cmdlet:Parameter
# Value =
# Default value for parameter
# * (Wildcard) can be used

$PSDefaultParameterValues = @{
'Stop-Process:ErrorAction' = 'SilentlyContinue'
'*:ComputerName' = 'DC-01'
'Get-*:Path' = 'c:\windows'
}

在它的核心部分,有一个名为 $PSDefaultParametersValues 的哈希表。缺省情况下,这个变量不存在或者为空。如果您想将缺省参数复位为它们的缺省值,请运行以下代码:

1
PS> $PSDefaultParameterValues = $null

哈希表的键是 cmdlet 名和参数值,通过 *** 来分隔。支持通配符。哈希表的值是缺省参数值。

在上述例子中:

  • Stop-Process-ErrorAction 参数将总是使用 “SilentlyContinue” 值。
  • 所有使用 -ComputerName 参数的 cmdlet 将使用 “DC-01”。
  • 所有使用 Get 动词和 -Path 参数的 cmdlet 将默认使用 “C:\Windows” 值

缺省参数可能很方便,也可能很怪异。您不应在配置文件脚本中定义它们,因为您有可能忘了做过这件事,而下周会奇怪 cmdlet 执行的行为和预期不一致。

并且缺省参数只对 cmdlet 和高级函数有效。它们对简单函数是无效的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Start-SimpleFunction
{
param($ID=100)

"ID = $ID"
}

function Start-AdvancedFunction
{
[CmdletBinding()]
param($ID=100)

"ID = $ID"

}

$PSDefaultParameterValues = @{
"Start-*Function:ID" = 12345
}

以下是执行结果:

1
2
3
4
5
6
7
PS> Start-SimpleFunction
ID = 100

PS> Start-AdvancedFunction
ID = 12345

PS>

PowerShell 技能连载 - 速度差别:读取大型日志文件

需要读取大型日志文件时,例如,解析错误信息,PowerShell 既可以使用低内存占用的管道,也可以使用高内存占用的循环。不过,区别不仅在于内存的消耗,而且在速度上。

通过管道,不需要消耗太多的内存但是速度可能非常慢。通过传统的循环,脚本可以 10 倍甚至 100 倍的速度生成相同的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# make sure this file exists, or else
# pick a different text file that is very large
$path = 'C:\Windows\Logs\DISM\dism.log'
# SLOW
# filtering text file via pipeline (low memory usage)
Measure-Command {
$result = Get-Content -Path $Path | Where-Object { $_ -like '*Error*' }
}

# FAST
# filtering text by first reading in all
# content (high memory usage!) and then
# using a classic loop

Measure-Command {
$lines = Get-Content -Path $Path -ReadCount 0
$result = foreach ($line in $lines)
{
if ($line -like '*Error*') { $line }
}
}

PowerShell 技能连载 - 查看文件对应的可执行程序

多数事情可以由 PowerShell 的内置指令完成,但是那还不够,您总是可以借助内置的 Windows API。例如,如果您想查看某个文件关联的应用程序,请试试这段代码:

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
function Get-ExecutableForFile
{
param
(
[Parameter(Mandatory)]
[string]
$Path
)

$Source = @"

using System;
using System.Text;
using System.Runtime.InteropServices;
public class Win32API
{
[DllImport("shell32.dll", EntryPoint="FindExecutable")]

public static extern long FindExecutableA(string lpFile, string lpDirectory, StringBuilder lpResult);

public static string FindExecutable(string pv_strFilename)
{
StringBuilder objResultBuffer = new StringBuilder(1024);
long lngResult = 0;

lngResult = FindExecutableA(pv_strFilename, string.Empty, objResultBuffer);

if(lngResult >= 32)
{
return objResultBuffer.ToString();
}

return string.Format("Error: ({0})", lngResult);
}
}

"@

Add-Type -TypeDefinition $Source -ErrorAction SilentlyContinue
[Win32API]::FindExecutable($Path)
}

以下是使用这个函数的方法:

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

PowerShell 技能连载 - 理解脚本块日志(第 7 部分)

这是关于 PowerShell 脚本块日志的迷你系列的第 7 部分。我们现在只需要一些能清理脚本快日志记录的清理工具,您需要管理员特权。

清理日之前请注意:这将清理整个 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
function Clear-PowerShellLog
{
<#
.SYNOPSIS
Ckears the entire PowerShell operational log including
script blog logging entries.
Administrator privileges required.

.DESCRIPTION
Clears the complete content of the log
Microsoft-Windows-PowerShell/Operational.
This includes all logged script block code.

.EXAMPLE
Clear-PowershellLog
Clears the entire log Microsoft-Windows-PowerShell/Operational.
#>
[CmdletBinding(ConfirmImpact='High')]
param()

try
{
$ErrorActionPreference = 'Stop'
wevtutil cl Microsoft-Windows-PowerShell/Operational
}
catch
{
Write-Warning "Administrator privileges required. Run this command from an elevated PowerShell."