PowerShell 技能连载 - 确认重复的 CSV 表头(第一部分)

CSV 文件只是文本文件,所以可以很容易地提取它的第一行并检查它的表头。如果您手头没有一个 CSV 文件,这行代码可以快速帮您创建一个:

1
2
3
PS C:\> Get-Process | Export-Csv -Path $env:temp\test.csv -NoTypeInformation -Encoding UTF8 -UseCulture

PS C:\>

现在您可以分析它的表头。这个简单的方法告诉您 CSV 文件中是否有重复的标题(在这个例子中显然不存在)。这段代码假设您的 CSV 文件分隔符是逗号。如果使用一个不同的分隔符,请调整用于分割的字符:

1
2
3
4
5
6
7
8
9
10
11
$headers = Get-Content $env:temp\test.csv | Select-Object -First 1
$duplicates = $headers.Split(',') | Group-Object -NoElement | Where-Object {$_.Count -ge 2}
if ($duplicates.Count -eq 0)
{
Write-Host 'You are safe!'
}
else
{
Write-Warning 'There are duplicate columns in your CSV file:'
$duplicates
}

结果如预想的:

1
2
3
You are safe!

PS C:\>

如果您好奇当遇到重复的标题时会如何失败,请试试这段代码:

1
PS C:\> driverquery /V /FO CSV | Set-Content -Path $env:temp\test.csv -Encoding UTF8

如果您在一个的文系统中运行这段代码,结果将会类似这样:

1
2
3
4
5
WARNUNG: There are  duplicate columns in your CSV file:

Count Name
----- ----
2 "Status"

显然,在本地化时,Microsoft 将 “State” 和 “Status” 两个单词都翻译成了德文的 “Status”,造成了重复的列标题。

PowerShell 技能连载 - 用区域性固定的方式序列化日期和时间

当您保存日期和时间到文本中时,例如导出到 CSV 时,或创建文本报告时,DateTime 对象将会按照您的区域设置转换为相应的日期和时间格式:

1
2
3
4
5
6
7
8
9
10
11
PS> $date = Get-Date -Date '2017-02-03 19:22:11'

PS> "$date"
02/03/2017 19:22:11

PS> $date.ToString()
03.02.2017 19:22:11

PS> Get-Date -Date $date -DisplayHint DateTime

Freitag, 3. Februar 2017 19:22:11

这些都是和区域有关的格式,所以当其他人打开您的数据,将它转换为真实的日期时间可能会失败。这就是为什么推荐将日期时间信息保存为文本时将它转换为区域无关的 ISO 格式:

1
2
3
4
PS> Get-Date -Date $date -Format 'yyyy-MM-dd HH:mm:ss'
2017-02-03 19:22:11

PS>

该 ISO 格式重视能正确地转回 DateTime 对象,无论您的机器用的是什么语言:

1
2
3
PS> [DateTime]'2017-02-03 19:22:11'

Friday, February 3, 2017 19:22:11

另外,这种设计保证它们在使用字母排序时顺序是正确的。

PowerShell 技能连载 - 将时钟周期转换为日期和时间(第二部分)

在前一个技能中我们解释了如何将用时钟周期数表达的日期时间转换为真实的 DateTime 格式。然而,现实中有两种不同的时钟周期格式,以下是如何转换数字型日期时间信息的概述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS> $date = Get-Date -Date '2017-02-03 19:22:11'
PS> $ticks = $date.Ticks
PS> $ticks
636217465310000000

PS> [DateTime]$ticks
Friday, February 3, 2017 19:22:11

PS> [DateTime]::FromBinary($ticks)
Friday, February 3, 2017 19:22:11

PS> [DateTime]::FromFileTime($ticks)
Friday, February 3, 3617 20:22:11

PS> [DateTime]::FromFileTimeUtc($ticks)
Friday, February 3, 3617 19:22:11

如您所见,将时钟周期转换为 DateTime 和执行 FromBinary() 静态方法的效果是一样的。但是 FromeFileTime() 做了什么?它似乎把你发送到了遥远的将来。

这个例子显示了到底发生了什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS> $date1 = [DateTime]::FromBinary($ticks)
PS> $date2 = [DateTime]::FromFileTime($ticks)
PS> $date2 - $date1

Days : 584388
Hours : 1
Minutes : 0
Seconds : 0
Milliseconds : 0
Ticks : 504911268000000000
TotalDays : 584388,041666667
TotalHours : 14025313
TotalMinutes : 841518780
TotalSeconds : 50491126800
TotalMilliseconds : 50491126800000

PS> ($date2 - $date1).Days / 365.25
1599,96714579055

FromeFileTime() 只是增加了 1601 年(因为闰年,实际计算结果略有出入)。Windows 的某些部分(例如 Active Directory)从 1601 年 1 月 1 日开始计算日期。对于这些情况,请使用 FromeFileTime() 来获取正确的日期时间。

PowerShell 技能连载 - 将时钟周期转换为日期和时间(第一部分)

有时候您可能会遇到一些奇怪的日期和时间格式,它们可能用的是类似这样的 64 位 integer 数值:636264671350358729。

如果您想将这样的“时钟周期”(Windows 中最小的时间片),只需要将数字转换为 DateTime 类型:

1
2
3
PS> [DateTime]636264671350358729

Thursday, March 30, 2017 10:38:55

类似地,要将一个日期转换为时钟周期,请试试这段代码:

1
2
3
4
PS> $date = Get-Date -Date '2017-02-03 19:22:11'

PS> $date.Ticks
636217465310000000

比如说,您可以利用这个时钟周期来将日期和时间序列化成非特定区域的格式。

PowerShell 技能连载 - 直接导入证书(第二部分)

在前一个技能中我们演示了如何在任何版本的 PowerShell 中用 .NET 方法导入数字证书。新版本的 PowerShell 有一个 “PKI” module,其中包括了 Import-Certificate cmdlet,导入证书变得更简单了。

1
2
3
4
#requires -Version 2.0 -Modules PKI
# importing to personal store
$Path = 'C:\Path\To\CertFile.cer'
Import-Certificate -FilePath $Path -CertStoreLocation Cert:\CurrentUser\My

请注意 Import-Certificate 如何通过 -CertStoreLocation 指定目标存储位置。这个命令返回导入的证书。

PowerShell 技能连载 - 直接导入证书(第一部分)

可以在任意版本的 PowerShell 中用 .NET 方法将证书安装到计算机中。这将导入一个证书文件到个人存储中:

1
2
3
4
5
6
# importing to personal store
$Path = 'C:\Path\To\CertFile.cer'
$Store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList My, CurrentUser
$Store.Open('ReadWrite')
$Store.Add($Path)
$Store.Close()

您可以打开证书管理器证实这一点:

1
PS C:\> certmgr.msc

如果您想将证书导入到一个不同的存储位置,只需要调整创建存储对象的参数即可。

PowerShell 技能连载 - 请注意别名

您能指出这段代码的错误吗?

1
2
3
4
5
6
PS C:\> function r { "This never runs" }

PS C:\> r
function r { "This never runs" }

PS C:\>

如果您执行函数 “r”,它只会返回函数的源代码。

错误的原因是函数名 “r” 和内置的别名冲突:

1
2
3
4
5
6
7
8
PS C:\> Get-Alias r

CommandType Name Version Source
----------- ---- ------- ------
Alias r -> Invoke-History


PS C:\>

所以请始终确保知道内置的别名——它们的优先级永远比函数或其它命令高。更好的做法是,按照最佳实践,始终用 Verb-Noum 的方式来命名您的函数。

PowerShell 技能连载 - 处理长文件路径

以前,当路径长于 256 字符时,Windows 文件系统有时会变得缓慢。在 PowerShell Gallery 有一个 module,增加了一系列 cmdlet,可以快速搜索文件系统,并且支持任意长度的路径。

如果您使用 PowerShell 5 或安装了 PowerShellGet(www.powershellgallery.com),那么您可以从 PowerShell Gallery 中下载和安装 “PSAlphaFS” module:

1
Install-Module -Name PSAlphaFS -Scope CurrentUser

不幸的是,这些 cmdlet 似乎需要完整的管理员特权,而对普通用户会抛出异常。如果您是管理员,您可以以这种方式查找长路径的文件:

1
2
Get-LongChildItem -Path c:\windows -Recurse -File |
Where-Object { $_.FullName.Length -gt 250 }

PowerShell 技能连载 - 检测字符代码 0

有些时候,字符串适用 “\0“ 作为分隔符。不像其它大多数分隔符,这个分隔符并不显示在文本输出中,但仍然可以用于分割文本。

PowerShell 可以处理包含字符代码 0 的字符串。它用反斜杠后跟着数字 0 来表示。请注意文本需要放在双引号之内,才能将反斜杠序列转换为字节 0。

以下是一个演示如何分割 \0 分割的文本的例子:

1
2
3
4
5
6
# create a sample text
$text = "Part 1`0Part 2`0Part 3"
# delimiter does not show in output...
$text
# ...but can be used to split:
$text -split "`0"

PowerShell 技能连载 - 自动定义函数的别名

您也许知道 PowerShell 支持命令的别名。但是您是否知道也可以在函数定义内部为 PowerShell 函数定义别名(PowerShell 4 引入的功能)呢?让我们来看看:

1
2
3
4
5
6
7
8
function Get-AlcoholicBeverage
{
[Alias('Beer','Drink')]
[CmdletBinding()]
param()

"Here is your beer."
}

这个函数的“正式”名称是 Get-AlcoholicBeverage,但是这个函数也可以通过 “Beer“ 和 “Drink“ 别名来引用。在函数定义时,PowerShell 自动增加了这些别名:

1
2
3
4
CommandType     Name
----------- ----
Alias Beer -> Get-AlcoholicBeverage
Alias Drink -> Get-AlcoholicBeverage