PowerShell 技能连载 - 使用超棒的 Export-Excel Cmdlet(第 5 部分)

这是我们关于 Doug Finke 的强大而免费的 “ImportExcel” PowerShell 模块的迷你系列文章的第 5 部分。在学习这个技能之前,请确保安装了该模块:

1
PS> Install-Module -Name ImportExcel -Scope CurrentUser -Force

在第 4 部分中,我们研究了由于在输入数据中包含数组而导致的误读数据。正如您所看到的,您只需要使用 -join 操作符将数组转换为字符串,Excel 就可以正确地显示数组,即逗号分隔值的列表。

但是,如果希望在单独的行中显示数组元素,并使用换行符呢?

默认情况下,Excel 只会在选定单元格的输入框中显示单独的行,而不是在所有单元格中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# get some raw data that contains arrays
$rawData = Get-EventLog -LogName System -Newest 10 |
Select-Object -Property TimeWritten, ReplacementStrings, InstanceId


# create this Excel file
$Path = "$env:temp\report.xlsx"
# make sure the file is deleted so we have no
# effects from previous data still present in the
# file. This requires that the file is not still
# open and locked in Excel
$exists = Test-Path -Path $Path
if ($exists) { Remove-Item -Path $Path}

$sheetName = 'Testdata'
$rawData |
ForEach-Object {
# convert column "ReplacementStrings" from array to string
$_.ReplacementStrings = $_.ReplacementStrings -join "`r`n"
# return the changed object
$_
} |
Export-Excel -Path $path -ClearSheet -WorksheetName $sheetName -Show

当您运行这段代码时,”ReplacementStrings” 中的数组将会正确地转换为多行文本,但是您不会在工作表中看到它。只有当您单击某个单元格时才会看到输入区域中显示多行文本。

当您把我们前面部分的信息组合起来时,可以很容易地对 Excel 文件进行后期处理,并像这样将单元格格式化为“文本”和“自动换行”:

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
# get some raw data that contains arrays
$rawData = Get-EventLog -LogName System -Newest 10 |
Select-Object -Property TimeWritten, ReplacementStrings, InstanceId


# create this Excel file
$Path = "$env:temp\report.xlsx"
# make sure the file is deleted so we have no
# effects from previous data still present in the
# file. This requires that the file is not still
# open and locked in Excel
$exists = Test-Path -Path $Path
if ($exists) { Remove-Item -Path $Path}

$sheetName = 'Testdata'

# save the Excel object model by using -PassThru instead of -Show
$excel = $rawData |
ForEach-Object {
# convert column "ReplacementStrings" from array to string
$_.ReplacementStrings = $_.ReplacementStrings -join "`r`n"
# return the changed object
$_
} |
Export-Excel -Path $path -ClearSheet -WorksheetName $sheetName -AutoSize -PassThru

#region Post-process the column with the misinterpreted formulas
# remove the region to repro the original Excel error
$sheet1 = $excel.Workbook.Worksheets[$sheetName]
# reformat cell to number type "TEXT" with WordWrap and AutoSize
Set-Format -Address $sheet1.Cells['B:B'] -NumberFormat 'Text' -WrapText -AutoSize
#endregion

Close-ExcelPackage -ExcelPackage $excel -Show

PowerShell 技能连载 - 使用超棒的 Export-Excel Cmdlet(第 4 部分)

这是我们关于 Doug Finke 的强大而免费的 “ImportExcel” PowerShell 模块的迷你系列文章的第 4 部分。在学习这个技能之前,请确保安装了该模块:

1
PS> Install-Module -Name ImportExcel -Scope CurrentUser -Force

在第 3 部分中,我们研究了由于公式自动转换而导致的错误解析数据,并研究了后期处理单个单元格格式的方式。让我们检查一下数组引起的问题。

以下是一些重现该现象的代码。在我们的示例中,这是最后 10 条系统事件的事件日志数据,它恰好包含了一个数组(替换字符串),并且显示完全不正常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# get some raw data that contains arrays
$rawData = Get-EventLog -LogName System -Newest 10 |
Select-Object -Property TimeWritten, ReplacementStrings, InstanceId


# create this Excel file
$Path = "$env:temp\report.xlsx"
# make sure the file is deleted so we have no
# effects from previous data still present in the
# file. This requires that the file is not still
# open and locked in Excel
$exists = Test-Path -Path $Path
if ($exists) { Remove-Item -Path $Path}

$sheetName = 'Testdata'
$rawData |
Export-Excel -Path $path -ClearSheet -WorksheetName $sheetName -Show

当 Excel 打开时,您可以看见 “ReplacementStrings” 列只显示数据类型 (System.String[]) 而不是实际的数据。这是 Excel 遇到数组的通常行为,所以 Export-Excel 对此无能为力。

相反地,在将将数组通过管道输出到 Export-Excel 命令之前转换为字符串是您的责任——用 -join 操作符可以很容易实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# get some raw data that contains arrays
$rawData = Get-EventLog -LogName System -Newest 10 |
Select-Object -Property TimeWritten, ReplacementStrings, InstanceId


# create this Excel file
$Path = "$env:temp\report.xlsx"
# make sure the file is deleted so we have no
# effects from previous data still present in the
# file. This requires that the file is not still
# open and locked in Excel:
$exists = Test-Path -Path $Path
if ($exists) { Remove-Item -Path $Path}

$sheetName = 'Testdata'
$rawData |
ForEach-Object {
# convert column "ReplacementStrings" from array to string
$_.ReplacementStrings = $_.ReplacementStrings -join ','
# return the changed object
$_
} |
Export-Excel -Path $path -ClearSheet -WorksheetName $sheetName -Show

当您做了这步操作之后,包含数组的属性在 Excel 中也可以正确显示。-join 对任何对象都有效。只需要确保指定了分割数组元素的分隔符。

PowerShell 技能连载 - 使用超棒的 Export-Excel Cmdlet(第 3 部分)

这是我们关于 Doug Finke 的强大而免费的 “ImportExcel” PowerShell 模块的迷你系列文章的第 3 部分。在学习这个技能之前,请确保安装了该模块:

1
PS> Install-Module -Name ImportExcel -Scope CurrentUser -Force

在第 2 部分中,我们检查了由于数字自动转换导致的错误解释。当原始数据”看起来像“ Excel 公式时会导致另一个问题,它们会被转换为公式并且会在等等打开时出现问题。

以下是重现该问题的示例:一些记录包含以 “=)” 开头的文本,会导致 Excel 认为它是一个公式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# any object-oriented data will do
# we create some sample records via CSV
# to mimick specific issues
$rawData = @'
Data,Name
Test, Tobias
=), Mary
=:-(), Tom
'@ | ConvertFrom-Csv

# create this Excel file
$Path = "$env:temp\report.xlsx"
# make sure the file is deleted so we have no
# effects from previous data still present in the
# file. This requires that the file is not still
# open and locked in Excel
$exists = Test-Path -Path $Path
if ($exists) { Remove-Item -Path $Path}

$rawData |
Export-Excel -Path $path -ClearSheet -WorksheetName Processes -Show

当您运行这段代码时,Excel 将打开但是立即报告非法格式。原始数据将会丢失。

这个问题无法通过一个开关参数解决。相反,您需要手动重新格式化单元格,这给了您很大的灵活性。以下是总体的策略:

  • 使用 Export-Excel 创建 .xlsx 文件,但不是指定 -Show(在 Excel 中打开文件),而是使用 -PassThru。这样就得到了 Excel 对象模型。
  • 使用对象模型对单元格进行任意更改
  • 使用 Close-ExcelPackage 将更改保存到文件中。您现在可以指定 -Show,并在 Excel 中打开结果。
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
# any object-oriented data will do
# we create some sample records via CSV
# to mimick specific issues
$rawData = @'
Data,Name
Test, Tobias
=), Mary
=:-(), Tom
'@ | ConvertFrom-Csv

# create this Excel file
$Path = "$env:temp\report.xlsx"
# make sure the file is deleted so we have no
# effects from previous data still present in the
# file. This requires that the file is not still
# open and locked in Excel
$exists = Test-Path -Path $Path
if ($exists) { Remove-Item -Path $Path}

$sheetName = 'Testdata'
$excel = $rawData |
Export-Excel -Path $path -ClearSheet -WorksheetName $sheetName -PassThru



#region Post-process the column with the misinterpreted formulas
# remove the region to repro the original Excel error
$sheet1 = $excel.Workbook.Worksheets[$sheetName]

# take all cells from row "A"...
$sheet1.Cells['A:A'] |
# ...that are currently interpreted as a formula...
Where-Object Formula |
ForEach-Object {
# ...construct the original content which is the formula
# plus a prepended "="
$newtext = ('={0}' -f $_.Formula)
# reformat cell to number type "TEXT"
Set-Format -Address $_ -NumberFormat 'Text' -Value 'dummy'
# assign the original content to the cell (this cannot be done using Set-Format)
$_.Value = $newtext
}
#endregion

Close-ExcelPackage -ExcelPackage $excel -Show

当您运行这段代码时,Excel 工作簿打开时不会报错,并且第一列能够正确地显示内容。这是由于我们显式地将第一列格式化为“文本”。然后,一旦格式被设置为“文本”,那么公式内容就会作为单元格值插入。

您不会受到“公式”错误信息,也不必通过在其周围添加引号来“屏蔽”内容。

这个示例演示了如何后期处理 Excel 工作簿并且在将结果保存到文件并在 Excel 中打开结果之前增加、更改、重新格式化独立的单元格。

PowerShell 技能连载 - 使用超棒的 Export-Excel Cmdlet(第 2 部分)

这是我们关于 Doug Finke 的强大而免费的 “ImportExcel” PowerShell 模块的迷你系列文章的第 2 部分。在学习这个技能之前,请确保安装了该模块:

1
PS> Install-Module -Name ImportExcel -Scope CurrentUser -Force

当您导出数据到 Excel 文件中时,您有时可能会遇到 Excel 错误解释的数据。例如,电话号码常常被错误地解释为数字型数据。以下是一个重现该问题的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# any object-oriented data will do
# we create some sample records via CSV
# to mimick specific issues
$rawData = @'
Phone,Name
+4915125262524, Tobias
0766256725672, Mary
00496253168722567, Tom
'@ | ConvertFrom-Csv

# create this Excel file
$Path = "$env:temp\report.xlsx"
# make sure the file is deleted so we have no
# effects from previous data still present in the
# file. This requires that the file is not still
# open and locked in Excel
$exists = Test-Path -Path $Path
if ($exists) { Remove-Item -Path $Path}

$rawData |
Export-Excel -Path $path -ClearSheet -WorksheetName Processes -Show

如您所见,当 Excel 打开时,电话号码自动转换为整形。

要避免这个自动转换,请使用 -NoNumberConversion 参数,并且指定不需要转换的列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# any object-oriented data will do
# we create some sample records via CSV
# to mimick specific issues
$rawData = @'
Phone,Name
+4915125262524, Tobias
0766256725672, Mary
00496253168722567, Tom
'@ | ConvertFrom-Csv

# create this Excel file
$Path = "$env:temp\report.xlsx"
# make sure the file is deleted so we have no
# effects from previous data still present in the
# file. This requires that the file is not still
# open and locked in Excel
$exists = Test-Path -Path $Path
if ($exists) { Remove-Item -Path $Path}

$rawData |
Export-Excel -Path $path -ClearSheet -WorksheetName Processes -Show -NoNumberConversion Phone

现在,”Phone” 列不再处理为数字,电话号码显示正常了。

PowerShell 技能连载 - 使用超棒的 Export-Excel Cmdlet(第 1 部分)

Doug Finke 创建了一个非常棒的 PowerShell 模块 ImportExcel,它提供了从 Microsoft Excel 导入和导出数据所需的所有命令。它不需要安装Office。

我们不能涵盖这个模块提供的所有内容,但在本文中,我们将为您提供启动和运行它的基础知识,在后续的技巧中,我们将讨论一些格式化技巧。

要使用 Excel 命令,只需要下载并安装免费的模块:

1
PS> Install-Module -Name ImportExcel -Scope CurrentUser -Force

首次运行时,您可能必须同意下载一个 “NuGet” 开源DLL。命令完成后,您现在可以访问大量新的 Excel 命令,其中最重要的是 Export-Excel

您现在可以通过管道将数据直接传给一个 Excel 文件,并且假设在 Microsoft Office 已经安装的情况下,您甚至可以在 Excel 中打开并显示文件(创建 .xlsx 文件不需要 Office)。

以下是一个简单的示例:

1
2
3
$Path = "$env:temp\report.xlsx"
Get-Process | Where-Object MainWindowTitle |
Export-Excel -Path $path -ClearSheet -WorksheetName Processes -Show

就是这么简单。创建 Excel 文件从来没有这么容易过。不过,你要记住以下几点:

  • 在将数据通过管道传给 Export-Excel 之前,使用 Select-Object 选择要导出的属性。
  • 使用 -ClearSheet 清除以前的数据。如果省略此参数,新数据将附加到 .xlsx 文件中的现有数据之后。
  • 在创建具有相同名称的新文件之前,您可能需要考虑手动删除旧的 .xlsx 文件。否则,Export-Excel 可能会参考旧文件中的现有设置。

PowerShell 技能连载 - 自动创建 HTTP 响应码清单

在前一个示例中我们学习了如何自动将数值型的 HTTP 响应码转换为描述性的文本,只需要将它们转换为 System.Net.HttpStatusCode 即可。

1
2
PS> [System.Net.HttpStatusCode]500
InternalServerError

这是因为 System.Net.HttpStatusCode 是一个所谓的“枚举”,其作用类似于“查找表”。您可以轻松地转储枚举的所有成员,例如创建一个 HTTP 响应代码表:

1
2
3
4
5
6
7
[Enum]::GetValues([System.Net.HttpStatusCode]) |
ForEach-Object {
[PSCustomObject]@{
Code = [int]$_
Description = $_.toString()
}
}

以上是创建一个最常见的 HTTP 响应码所需要的所有代码。

Code Description
---- -----------
 100 Continue
 101 SwitchingProtocols
 200 OK
 201 Created
 202 Accepted
 203 NonAuthoritativeInformation
 204 NoContent
 205 ResetContent
 206 PartialContent
 300 MultipleChoices
 300 MultipleChoices
 301 MovedPermanently
 301 MovedPermanently
 302 Redirect
 302 Redirect
 303 SeeOther
 303 SeeOther
 304 NotModified
 305 UseProxy
 306 Unused
 307 TemporaryRedirect
 307 TemporaryRedirect
 400 BadRequest
 401 Unauthorized
 402 PaymentRequired
 403 Forbidden
 404 NotFound
 405 MethodNotAllowed
 406 NotAcceptable
 407 ProxyAuthenticationRequired
 408 RequestTimeout
 409 Conflict
 410 Gone
 411 LengthRequired
 412 PreconditionFailed
 413 RequestEntityTooLarge
 414 RequestUriTooLong
 415 UnsupportedMediaType
 416 RequestedRangeNotSatisfiable
 417 ExpectationFailed
 426 UpgradeRequired
 500 InternalServerError
 501 NotImplemented
 502 BadGateway
 503 ServiceUnavailable
 504 GatewayTimeout
 505 HttpVersionNotSupported

这种方法适用于您可能遇到的任何枚举。只需更改枚举数据类型的名称即可。这个例子转储可用的控制台颜色代码:

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
[Enum]::GetValues([System.ConsoleColor]) |
ForEach-Object {
[PSCustomObject]@{
Code = [int]$_
Description = $_.toString()
}
}



Code Description
---- -----------
0 Black
1 DarkBlue
2 DarkGreen
3 DarkCyan
4 DarkRed
5 DarkMagenta
6 DarkYellow
7 Gray
8 DarkGray
9 Blue
10 Green
11 Cyan
12 Red
13 Magenta
14 Yellow
15 White

PowerShell 技能连载 - 转换 HTTP 响应码

在前一个示例中我们创建了一个小的 PowerShell 函数,它能够检查 Web 网络的可用性,并 HTTP 返回码会作为测试结果的一部分返回。让我们看看如何可以轻松地将这个数字代码转换为有意义的文本消息。

以下还是那个测试网站的函数:

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

Add-Type -AssemblyName System.Web

$check = "https://isitdown.site/api/v3/"
$encoded = [System.Web.HttpUtility]::UrlEncode($url)
$callUrl = "$check$encoded"

Invoke-RestMethod -Uri $callUrl |
Select-Object -Property Host, IsItDown, Response_Code
}

以下是典型的结果:

1
2
3
4
5
PS C:\> Test-Url -Url powershellmagazine.com

host isitdown response_code
---- -------- -------------
powershellmagazine.com False 200

在这个示例中,响应代码是 “200”,恰好代表 “OK”。如果您希望将 HTTP 响应码转换为文本,只需要将数据类型转换为 [System.Net.HttpStatusCode]。这样就可以了:

1
2
PS C:\> 200 -as [System.Net.HttpStatusCode]
OK

以下是包含该转换过程的版本:

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
function Test-Url
{
param
(
[Parameter(Mandatory,ValueFromPipeline)]
[string]
$Url
)

Add-Type -AssemblyName System.Web

$check = "https://isitdown.site/api/v3/"
$encoded = [System.Web.HttpUtility]::UrlEncode($url)
$callUrl = "$check$encoded"
$response = @{
Name = 'Response'
Expression = {
'{0} ({1})' -f
($_.Response_Code -as [System.Net.HttpStatusCode]),
$_.Response_Code
}
}
Invoke-RestMethod -Uri $callUrl |
Select-Object -Property Host, IsItDown, $response
}

结果如下:

1
2
3
4
5
PS C:\> Test-Url -Url powershellmagazine.com

host isitdown Response
---- -------- --------
powershellmagazine.com False OK (200)

请注意计算字段 “Response” 现在体现的是原始的数值型响应码和对应的友好文本。

PowerShell 技能连载 - 测试网站的可用性

当一个网站不可用时,通常的问题是仅仅您不能访问该网站,还是其他所有人都不能访问。PowerShell 可以调用一个 Web Service 为您检查 web 站点的可用性。下面是一个简单的包装函数:

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

Add-Type -AssemblyName System.Web

$check = "https://isitdown.site/api/v3/"
$encoded = [System.Web.HttpUtility]::UrlEncode($url)
$callUrl = "$check$encoded"

Invoke-RestMethod -Uri $callUrl |
Select-Object -Property Host, IsItDown, Response_Code
}

它将调用一个 RESTful API 并且通过 URL 参数进行检查。这是为什么待测试的 URL 需要进行 URL 编码,这段代码调用 Invoke-RestMethod 并且以一个对象的形式接收测试结果。

1
2
3
4
5
PS C:\> Test-Url -Url powershellmagazine.com

host isitdown response_code
---- -------- -------------
powershellmagazine.com False 200

请注意这个示例中使用的 Web Service 是免费的,并且不需要注册或 API 密钥。缺点是该 Web Service 是限流的它可能会返回一个异常,提示您提交了太多请求。当这种情况发生时,只需要等待一阵子再重试。

PowerShell 技能连载 - 通过 Web Service 做单位转换

通过 PowerShell 访问 RESTful Web Service 十分容易:只需要将您的发送数据发给公开的 Web Service,并且接收结果即可。

以下是三个 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
40
41
42
43
function Convert-InchToCentimeter
{
param
(
[Parameter(Mandatory)]
[Double]
$Inch
)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = 'https://ucum.nlm.nih.gov/ucum-service/v1/ucumtransform/{0}/from/%5Bin_i%5D/to/cm' -f $Inch
$result = Invoke-RestMethod -Uri $url -UseBasicParsing
$result.UCUMWebServiceResponse.Response
}


function Convert-FootToMicrometer
{
param
(
[Parameter(Mandatory)]
[Double]
$Foot
)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = 'https://ucum.nlm.nih.gov/ucum-service/v1/ucumtransform/{0}/from/%5Bft_i%5D/to/um' -f $Foot
$result = Invoke-RestMethod -Uri $url -UseBasicParsing
$result.UCUMWebServiceResponse.Response
}


function Convert-GramToOunce
{
param
(
[Parameter(Mandatory)]
[Double]
$Gram
)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = 'https://ucum.nlm.nih.gov/ucum-service/v1/ucumtransform/{0}/from/g/to/%5Boz_ap%5D' -f $Gram
$result = Invoke-RestMethod -Uri $url -UseBasicParsing
$result.UCUMWebServiceResponse.Response
}

假设您有 Internet 连接,然后做单位换算就是一个函数调用那么简单了:

1
2
3
4
5
PS C:\> Convert-GramToOunce -Gram 230

SourceQuantity SourceUnit TargetUnit ResultQuantity
-------------- ---------- ---------- --------------
230.0 g [oz_ap] 7.3946717

需要注意的点有:

  • 您需要允许 Tls12 来允许 HTTPS 连接(参考代码)
  • 您需要遵守 Web Service 规定的规则,即当它需要整个数数时,您不能提交小数。

更多的转换功能请参考 https://ucum.nlm.nih.gov/ucum-service.html#conversion,您可以使用以上提供的函数作为模板来创建更多的转换函数。

PowerShell 技能连载 - 验证 Active Directory 凭据

PowerShell 可以通过 Active Directory 验证 AD 用户名和密码:

1
2
3
4
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$account = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([DirectoryServices.AccountManagement.ContextType]::Domain, $env:userdomain)

$account.ValidateCredentials('user12', 'topSecret')

请注意这种方法只能作为诊断目的。它以明文的方式输入密码。