PowerShell 技能连载 - 弹出 CD 驱动器

以下是一个用 WMI 弹出 CD 驱动器的小函数。它首先向 WMI 请求所有的 CD 驱动器,然后使用 explorer 对象模型导航到该驱动器并调用它的 “Eject” 上下文菜单项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Eject-CD
{
$drives = Get-WmiObject Win32_Volume -Filter "DriveType=5"
if ($drives -eq $null)
{
Write-Warning "Your computer has no CD drives to eject."
return
}
$drives | ForEach-Object {
(New-Object -ComObject Shell.Application).Namespace(17).ParseName($_.Name).InvokeVerb("Eject")
}
}

Eject-CD

PowerShell 技能连载 - 检测 CSV 的分隔符

当使用 Import-Csv 导入一个 CSV 文件,需要指定一个分隔符。如果用错了,显然会导入失败。您需要事先知道 CSV 文件使用的分隔符。

以下是一个简单的实践,展示了如何判断一个给定的 CSV 文件的分隔符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Get-CsvDelimiter($Path)
{
# get the header line
$headerLine = Get-Content $Path | Select-Object -First 1

# examine header line per character
$headerline.ToCharArray() |
# find all non-alphanumeric characters
Where-Object { $_ -notlike '[a-z0-9äöüß"()]' } |
# find the one that occurs most often
Group-Object -NoElement |
Sort-Object -Descending -Property Count |
# return it
Select-Object -First 1 -ExpandProperty Name
}

以下是一个测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS> Get-Date | Export-Csv -Path $env:temp\test.csv -NoTypeInformation

PS> Get-CsvDelimiter -Path $env:temp\test.csv
,

PS> Get-Date | Export-Csv -Path $env:temp\test.csv -NoTypeInformation -UseCulture

PS> Get-CsvDelimiter -Path $env:temp\test.csv
;

PS> Get-Date | Export-Csv -Path $env:temp\test.csv -NoTypeInformation -Delimiter '#'

PS> Get-CsvDelimiter -Path $env:temp\test.csv
#

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

当一个 CSV 文件包含重复的表头时,它无法被导入。在前一个技能中我们掩饰了如何检测一个 CSV 文件中重复的表头。以下是一个自动更正重复项的实践。

第一步,您需要一个包含重复表头的 CSV 文件。例如在德文系统中,您可以这样创建一个文件:

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

快速打开该文件并检查它是否确实包含重复项。

1
PS C:\> notepad $env:temp\test.csv

如果没有重复项,请将某些表头重命名以制造一些重复项,并保存文件。

您现在可以用 Import-Csv 导入 CSV 文件了:

1
2
3
4
5
6
7
PS C:\>  Import-Csv -Path $env:temp\test.csv -Delimiter ','
Import-Csv : Element "Status" is present already.
In Zeile:1 Zeichen:1
+ Import-Csv -Path $env:temp\test.csv -Delimiter ','
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Import-Csv], ExtendedTypeSystemException
+ FullyQualifiedErrorId : AlreadyPresentPSMemberInfoInternalCollectionAdd,Microsoft.PowerShell.Commands.ImportCsvCommand

这是一个新的名为 Import-CsvWithoutDuplicate 的函数,可以自动处理重复的项:

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
function Import-CsvWithDuplicate($Path, $Delimiter=',', $Encoding='UTF8')
{
# get the header line and all header items
$headerLine = Get-Content $Path | Select-Object -First 1
$headers = $headerLine.Split($Delimiter)

# check for duplicate header names, and if found, add an incremented
# number to it
$dupDict = @{}
$newHeaders = @(foreach($header in $headers)
{
$incrementor = 1
$header = $header.Trim('"')
$newheader = $header

# increment numbers until the new name is unique
while ($dupDict.ContainsKey($newheader) -eq $true)
{
$newheader = "$header$incrementor"
$incrementor++
}

$dupDict.Add($newheader, $header)

# return the new header, producing a string array
$newheader
})

# read the CSV without its own headers..
Get-Content -Path $Path -Encoding $Encoding |
Select-Object -Skip 1 |
# ..and replace headers with newly created list
ConvertFrom-CSV -Delimiter $Delimiter -Header $newHeaders
}

通过它,您可以安全地导入 CSV 文件,不会遇到重复的表头:

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
PS C:\> Import-CsvWithDuplicate -Path $env:temp\test.csv -Delimiter ','


Modulname : 1394ohci
Anzeigename : OHCI-konformer 1394-Hostcontroller
Beschreibung : OHCI-konformer 1394-Hostcontroller
Treibertyp : Kernel
Startmodus : Manual
Status : Stopped
Status1 : OK
Beenden annehmen : FALSE
Anhalten annehmen : FALSE
Ausgelagerter Pool (Bytes) : 4.096
Code(Bytes) : 204.800
BSS(Bytes) : 0
Linkdatum : 16.07.2016 04:21:36
Pfad : C:\WINDOWS\system32\drivers\1394ohci.sys
Init(Bytes) : 4.096

Modulname : 3ware
Anzeigename : 3ware
Beschreibung : 3ware
Treibertyp : Kernel
Startmodus : Manual
Status : Stopped
Status1 : OK
Beenden annehmen : FALSE
Anhalten annehmen : FALSE
Ausgelagerter Pool (Bytes) : 0
(...)

如您所见,这个函数自动将第二个 “Status” 实例重命名为 “Status1”。

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 的方式来命名您的函数。