PowerShell 技能连载 - Get-ComputerInfo 和 systeminfo.exe 的对比(第 2 部分)

在 PowerShell 5 中,引入了一个名为 Get-ComputerInfo 的新的 cmdlet,它完成曾经 systeminfo.exe 的功能,而 Get-ComputerInfo 是直接面向对象的。没有本地化的问题:

1
$infos = Get-ComputerInfo

您现在可以查询您电脑独立的详情:

1
2
3
$infos.OsInstallDate
$infos.OsFreePhysicalMemory
$infos.BiosBIOSVersion

或者使用 Select-Object 来选择所有兴趣的属性:

1
$infos | Select-Object -Property OSInstallDate, OSFreePhysicalMemory, BiosBIOSVersion

在缺点方面,请考虑这一点:Get-ComputerInfo 是在 PowerShell 5 中引入的,您可以很容易地更新到该版本,或者将 PowerShell Core 与旧版本的 Windows PowerShell 并行使用。然而,Get-ComputerInfo 检索到的许多信息仅来自于最近的 Windows 操作系统中添加的 WMI 类。

如果您在 Windows 7 中更新到了 Windows PowerShell 5.1,Get-ComputerInfo 有可能不能正常工作。在旧的系统中,systeminfo.exe 是您的最佳依赖,而在新的操作系统中,Get-ComputerInfo 用起来方便得多。

PowerShell 技能连载 - Get-ComputerInfo 和 systeminfo.exe 的对比(第 1 部分)

在很长一段时间内,命令行工具 systeminfo.exe 提供了大量计算机的信息,并且可以通过一个小技巧返回面向对象的结果:

1
2
3
4
$objects = systeminfo.exe /FO CSV |
ConvertFrom-Csv

$objects.'Available Physical Memory'

从好的一方面来说,systeminfo.exe 基本上在所有 Windows 系统中都可用。从坏的一方面来说,结果是语言本地化的,并且属性名可能会成为一个问题:在英文的系统中,一个属性可能名为 ‘Available Physical Memory’ 而在一个德文系统中可能会不同。要使表头一致,您可以将它们去除并替换成自己的:

1
2
3
4
$headers = 1..30 | ForEach-Object { "Property$_" }
$objects = systeminfo.exe /FO CSV |
Select-Object -Skip 1 |
ConvertFrom-Csv -Header $headers

以下是执行结果:

PS> $objects


Property1  : DESKTOP-8DVNI43
Property2  : Microsoft Windows 10 Pro
Property3  : 10.0.18362 N/A Build 18362
Property4  : Microsoft Corporation
Property5  : Standalone Workstation
Property6  : Multiprocessor Free
Property7  : hello@test.com
Property8  : N/A
Property9  : 00330-50000-00000-AAOEM
Property10 : 9/3/2019, 11:42:41 AM
Property11 : 11/1/2019, 10:42:53 AM
Property12 : Dell Inc.
Property13 : XPS 13 7390 2-in-1
Property14 : x64-based PC
Property15 : 1 Processor(s) Installed.,[01]: Intel64 Family 6 Model 126 Stepping 5
             GenuineIntel ~1298 Mhz
Property16 : Dell Inc. 1.0.9, 8/2/2019
Property17 : C:\Windows
Property18 : C:\Windows\system32
Property19 : \Device\HarddiskVolume1
Property20 : de;German (Germany)
Property21 : de;German (Germany)
Property22 : (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
Property23 : 32,536 MB
Property24 : 19,169 MB
Property25 : 37,400 MB
Property26 : 22,369 MB
Property27 : 15,031 MB
Property28 : C:\pagefile.sys
Property29 : WORKGROUP
Property30 : \\DESKTOP-8DVNI43




PS> $objects.Property23
32,536 MB

您可以通过在 $headers 中构造一个自定义的属性名列表,任意对属性命名:

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
$headers = 'HostName',
'OSName',
'OSVersion',
'OSManufacturer',
'OSConfiguration',
'OSBuildType',
'RegisteredOwner',
'RegisteredOrganization',
'ProductID',
'OriginalInstallDate',
'SystemBootTime',
'SystemManufacturer',
'SystemModel',
'SystemType',
'Processors',
'BIOSVersion',
'WindowsDirectory',
'SystemDirectory',
'BootDevice',
'SystemLocale',
'InputLocale',
'TimeZone',
'TotalPhysicalMemory',
'AvailablePhysicalMemory',
'VirtualMemoryMaxSize',
'VirtualMemoryAvailable',
'VirtualMemoryInUse',
'PageFileLocations',
'Domain',
'LogonServer',
'Hotfix',
'NetworkCard',
'HyperVRequirements'

$objects = systeminfo.exe /FO CSV |
Select-Object -Skip 1 |
ConvertFrom-Csv -Header $headers

$objects.ProductID

PowerShell 技能连载 - 在 PowerShell 中安全地使用 WMI(第 4 部分)

在这个迷你系列中,我们在探索 Get-WmiObjectGet-CimInstance 之间的差异。未来的 PowerShell 版本不再支持 Get-WMIObject,因此,如果您尚未加入,则需要切换到 Get-CimInstance

在前一个部分中您学到了通过网络查询信息时有明显的区别,并且 Get-CimInstance 可以使用可充分定制且可复用的会话对象,来帮助网络访问速度更快以及消耗更少的资源。

由于 Get-CimInstance 工作机制类似 Web Service,而不像 Get-WmiObject 是基于 DCOM 的,这在返回的数据上有许多重要的含义。Get-CimInstance 总是通过序列化,所以您总是不可避免地收到一个副本,而不是原始对象。这是为什么 Get-CimInstance 永远不返回方法的原因。您永远只会获取到属性。

以下是一个可实践的示例:Win32_Process WMI 类有一个名为 GetOwner() 的方法返回进程的所有者。如果您希望找出谁登录到您的计算机,您需要查询 explorer.exe 进程并列出他们的所有者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# find all explorer.exe instances
Get-WmiObject -Class Win32_Process -Filter 'Name="explorer.exe"' |
ForEach-Object {
# call the WMI method GetOwner()
$owner = $_.GetOwner()
if ($owner.ReturnValue -eq 0)
{
# return either the process owner...
'{0}\{1}' -f $owner.Domain, $owner.User
}
else
{
# ...or the error code
'N/A (Error Code {0})' -f $owner.ReturnValue
}
} |
# remove duplicates
Sort-Object -Unique

如果您希望将这段代码转换为使用 Get-CimInstance,那么将无法访问 GetOwner() 方法,因为 Get-CimInstance 只返回一个属性集合。相应地需要执行 Invoke-CimMethod 方法来调用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# find all explorer.exe instances
Get-CimInstance -ClassName Win32_Process -Filter 'Name="explorer.exe"' |
ForEach-Object {
# call the WMI method GetOwner()
$owner = $_ | Invoke-CimMethod -MethodName GetOwner
if ($owner.ReturnValue -eq 0)
{
# return either the process owner...
'{0}\{1}' -f $owner.Domain, $owner.User
}
else
{
# ...or the error code
'N/A (Error Code {0})' -f $owner.ReturnValue
}
} |
# remove duplicates
Sort-Object -Unique

Invoke-CimMethod 或者需要和 CimInstance 配合使用,或者需要和原始的查询合并,这可以进一步简化代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# find all explorer.exe instances
Invoke-CimMethod -Query 'Select * From Win32_Process Where Name="explorer.exe"' -MethodName GetOwner |
ForEach-Object {
if ($_.ReturnValue -eq 0)
{
# return either the process owner...
'{0}\{1}' -f $_.Domain, $_.User
}
else
{
# ...or the error code
'N/A (Error Code {0})' -f $_.ReturnValue
}
} |
# remove duplicates
Sort-Object -Unique

Invoke-CimMethod 也可以调用静态的 WMI 方法,这些方法是属于 WMI 类的方法而不是属于一个独立的实例。如果您希望在本地或者远程创建一个新的进程(启动一个新的程序),那么可以使用这样的一行代码:

1
2
3
4
5
PS> Invoke-CimMethod -ClassName Win32_Process -MethodName "Create" -Arguments @{ CommandLine = 'notepad.exe'; CurrentDirectory = "C:\windows\system32" }

ProcessId ReturnValue PSComputerName
--------- ----------- --------------
3308 0

注意:如果您在一台远程计算机上启动了一个程序,它将会在您的隐藏登录会话中运行并且在屏幕上不可见。

PowerShell 技能连载 - 在 PowerShell 中安全地使用 WMI(第 3 部分)

在这个迷你系列中,我们在探索 Get-WmiObjectGet-CimInstance 之间的差异。未来的 PowerShell 版本不再支持 Get-WMIObject,因此,如果您尚未加入,则需要切换到 Get-CimInstance

在前一个部分中您学习了对于 WMI 类,两个 cmdlet 返回相同的基本信息,但是两个 cmdlet 添加的元数据属性差异显著,并且偶然情况下,属性的数据类型也不相同。例如,日期和时间类型在 Get-CimInstance 命令中返回的是 DateTime 类型,而在 Get-WmiObject 命令中返回的是字符串类型。不过总的来说,差异不大,容易调整。

当您在网络上查询,Get-WmiObjectGet-CimInstance 的差异就比较明显了:

  • Get-WmiObject 使用 DCOM 进行所有远程处理。它是硬连线的。DCOM 是一个旧的远程处理协议。它使用大量的资源,需要在防火墙中打开许多端口。
  • Get-CimInstance 缺省情况下使用的是 WinRM 并且工作方式类似 Web Service。不过,您可以通过多种方式改变这种行为,所以远程处理层可以和实际的 WMI 查询层分离。

当连到旧的服务器时,您可能会注意到这个区别,服务器能正确响应 Get-WmiObject 但是对于 Get-CimInstance 命令抛出异常。旧的服务器只支持旧的 DCOM 协议。

以下是您如何告诉 Get-CimInstance 回落到 Get-WmiObject 使用的旧式 DCOM 协议:

1
2
3
4
5
6
7
8
9
10
11
# replace server name to some server that you control
$server = 'SOMESERVER1'

# Get-CimInstance uses WinRM remoting by default
Get-CimInstance -ClassName Win32_BIOS -ComputerName $server

# telling Get-CimInstance to use the old DCOM to contact an old system
$options = New-CimSessionOption -Protocol Dcom
$session = New-CimSession -SessionOption $options -ComputerName $server
Get-CimInstance -ClassName Win32_BIOS -CimSession $session
Remove-CimSession -CimSession $session

如您所见,您可以配置一个 CimSession 对象,它是可以充分配置的。使用 CIMSession 对象而不是计算机名还有一个额外的好处:您可以在多个查询中复用该会话来节约许多时间。只需要确保结束时关闭会话即可。

PowerShell 技能连载 - 在 PowerShell 中安全地使用 WMI(第 2 部分)

在这个迷你系列中,我们在探索 Get-WmiObjectGet-CimInstance 之间的差异。未来的 PowerShell 版本不再支持 Get-WMIObject,因此,如果您尚未加入,则需要切换到 Get-CimInstance

在前面的部分中,您了解到两个 cmdlet 都返回相同的 WMI 类的基本信息,但是两个 cmdlet 添加的元数据属性有很大不同。现在让我们仔细看看它们返回的基本信息。

我们细化了测试脚本,也可以考虑数据类型,因此我们不仅查找属性名称,而且还考虑了这些属性返回的数据的类型。为此,我们检查属性“TypeNameOfValue”。

由于这是一个字符串,类型名并不一定是一致的。它们可能显示为 “Bool” 与 “Boolean”,或者 “String” 与 “System.String”。要使结果可以用来对比,代码使用了一个位于 $typeName 的计算属性来忽略类型命名空间,并且在一个 switch 语句中人工做调整。如果您恰好发现另一个名字显示不正确,只需要扩展 switch 语句。

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
# we are comparing this WMI class (feel free to adjust)
$wmiClass = 'Win32_OperatingSystem'

# get information about the WMI class Win32_OperatingSystem with both cmdlets
$a = Get-WmiObject -Class $wmiClass | Select-Object -First 1
$b = Get-CimInstance -ClassName $wmiClass | Select-Object -First 1

# create a calculated property that returns only the basic type name
# and omits the namespace
$typeName = @{
Name = 'Type'
Expression = {
$type = $_.TypeNameOfValue.Split('.')[-1].ToLower()
switch ($type)
{
'boolean' { 'bool' }
default { $type }
}
}
}

# ignore the metadata properties which we already know are different
$meta = '__CLASS','__DERIVATION','__DYNASTY','__GENUS','__NAMESPACE','__PATH','__PROPERTY_COUNT','__RELPATH','__SERVER','__SUPERCLASS','CimClass','CimInstanceProperties','CimSystemProperties','ClassPath','Container','Options','Properties','Qualifiers','Scope','Site','SystemProperties'

# return the properties and their data type. Add the origin so we later know
# which cmdlet emitted them
$aDetail = $a.PSObject.Properties |
# exclude the metadata we already know is different
Where-Object { $_.Name -notin $meta } |
# add the origin command as new property "Origin"
Select-Object -Property Name, $typeName, @{N='Origin';E={'Get-WmiObject'}}
$bDetail = $b.PSObject.Properties |
# exclude the metadata we already know is different
Where-Object { $_.Name -notin $meta } |
# add the origin command as new property "Origin"
Select-Object -Property Name, $typeName, @{N='Origin';E={'Get-CimInstance'}}

# compare differences
Compare-Object -ReferenceObject $aDetail -DifferenceObject $bDetail -Property Name, Type -PassThru |
Select-Object -Property Name, Origin, Type |
Sort-Object -Property Name

以下是执行结果:

Name           Origin          Type
----           ------          ----
InstallDate    Get-CimInstance ciminstance#datetime
InstallDate    Get-WmiObject   string
LastBootUpTime Get-CimInstance ciminstance#datetime
LastBootUpTime Get-WmiObject   string
LocalDateTime  Get-CimInstance ciminstance#datetime
LocalDateTime  Get-WmiObject   string
Path           Get-WmiObject   managementpath
  • Get-CimInstanceDateTime 对象的形式返回日期和时间,而 Get-WmiObject 以字符串的形式返回它们,字符串格式是内部的 WMI 格式
  • Get-WmiObject 增加了另一个名为 “Path” 的元数据属性

Get-CimInstance 处理日期和时间比 Get-WmiObject 更简单得多:

1
2
3
4
5
6
PS> $a.InstallDate
20190903124241.000000+120

PS> $b.InstallDate

Tuesday, September 3, 2019 12:42:41

通过以上代码,您现在有了一个方便的工具来测试您脚本使用的 WMI 类,并在从 Get-WmiObject 迁移到 Get-CimInstance 时标识可能返回不同数据类型的属性。

PowerShell 技能连载 - 在 PowerShell 中安全地使用 WMI(第 1 部分)

WMI (Windows Management Instrumentation) 是 Windows 操作系统的一部分并且广泛用于获取计算机系统的信息。PowerShell 之前引入了 Get-WmiObject 命令。在 PowerShell 3 中,增加了一个更现代的 Get-CimInstance 命令。

为了保持向后兼容,Windows PowerShell 始终保留了旧的 Get-WmiObject 命令,许多脚本开发者仍然在使用它而忽略了 Get-CimInstance。现在该是时候停止这个旧习惯因为 PowerShell Core (PowerShell 6, 7) 停止了对 Get-WmiObject 的支持。要确保脚本支持将来的 PowerShell 版本,您需要开始使用 Get-CimInstance 来代替 Get-WmiObject。

这刚开始看起来是微不足道的改变,实际上也确实是。对于简单的数据查询,您可能会通过 Get-CimInstance 来替换 Get-WmiObject:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PS> Get-WmiObject -Class Win32_BIOS

SMBIOSBIOSVersion : 1.0.9
Manufacturer : Dell Inc.
Name : 1.0.9
SerialNumber : 4ZKM0Z2
Version : DELL - 20170001

PS> Get-CIMInstance -Class Win32_BIOS

SMBIOSBIOSVersion : 1.0.9
Manufacturer : Dell Inc.
Name : 1.0.9
SerialNumber : 4ZKM0Z2
Version : DELL - 20170001

请注意在 Get-CimInstance-Class 参数实际上改名为 -ClassName,但是由于 PowerShell 允许在参数名称唯一的情况下使用缩写,所以您无须改变参数名称。

然而,实际上 Get-WmiObject 和 Get-CimInstance 并不是 100% 兼容,并且您需要知道许多重要的区别,尤其是当您计划改变已有的脚本时。在这个迷你系列中,我们将看到最重要的实际差异。

让我们看看两个命令返回的信息。以下是一个查看返回属性的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# we are comparing this WMI class (feel free to adjust)
$wmiClass = 'Win32_OperatingSystem'

# get information about the WMI class Win32_OperatingSystem with both cmdlets
$a = Get-WmiObject -Class $wmiClass | Select-Object -First 1
$b = Get-CimInstance -ClassName $wmiClass | Select-Object -First 1

# dump the property names and add the property "Origin" so you know
# which property was returned by which command:
$aDetail = $a.PSObject.Properties | Select-Object -Property Name, @{N='Origin';E={'Get-WmiObject'}}
$bDetail = $b.PSObject.Properties | Select-Object -Property Name, @{N='Origin';E={'Get-CimInstance'}}

# compare the results:
Compare-Object -ReferenceObject $aDetail -DifferenceObject $bDetail -Property Name -PassThru |
Sort-Object -Property Origin

以下是执行结果:

Name                  Origin          SideIndicator
----                  ------          -------------
CimClass              Get-CimInstance =>
CimInstanceProperties Get-CimInstance =>
CimSystemProperties   Get-CimInstance =>
Qualifiers            Get-WmiObject   <=
SystemProperties      Get-WmiObject   <=
Properties            Get-WmiObject   <=
ClassPath             Get-WmiObject   <=
Options               Get-WmiObject   <=
Scope                 Get-WmiObject   <=
__PATH                Get-WmiObject   <=
__NAMESPACE           Get-WmiObject   <=
__SERVER              Get-WmiObject   <=
__DERIVATION          Get-WmiObject   <=
__PROPERTY_COUNT      Get-WmiObject   <=
__RELPATH             Get-WmiObject   <=
__DYNASTY             Get-WmiObject   <=
__SUPERCLASS          Get-WmiObject   <=
__CLASS               Get-WmiObject   <=
__GENUS               Get-WmiObject   <=
Site                  Get-WmiObject   <=
Container             Get-WmiObject   <=

结果显示 metadata 中有显著的区别。Get-WmiObject 总是返回再起属性 “__Server”(两个下划线)中进行查询的计算机的名称而 Get-WmiObject 则是在 CimSystemProperties 中列出此信息:

1
2
3
4
5
6
7
8
PS> $b.CimSystemProperties

Namespace ServerName ClassName Path
--------- ---------- --------- ----
root/cimv2 DESKTOP-8DVNI43 Win32_Process

PS> $b.CimSystemProperties.ServerName
DESKTOP-8DVNI43

不过,好消息是,类的特定属性并没有不同,因此这两个命令都返回有关操作系统、BIOS 或您所查询的其它内容的相同基本信息。这一行返回相同的属性:

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
46
47
48
49
50
51
52
53
54
Compare-Object -ReferenceObject $aDetail -DifferenceObject $bDetail -Property Name -IncludeEqual -ExcludeDifferent -PassThru |  Sort-Object -Property Origin | Select-Object -Property Name, SideIndicator


Name SideIndicator
---- -------------
ProcessName ==
ParentProcessId ==
PeakPageFileUsage ==
PeakVirtualSize ==
PeakWorkingSetSize ==
Priority ==
PrivatePageCount ==
ProcessId ==
QuotaNonPagedPoolUsage ==
QuotaPagedPoolUsage ==
QuotaPeakNonPagedPoolUsage ==
PageFileUsage ==
QuotaPeakPagedPoolUsage ==
PSComputerName ==
ReadTransferCount ==
SessionId ==
Status ==
TerminationDate ==
ThreadCount ==
UserModeTime ==
VirtualSize ==
WindowsVersion ==
WorkingSetSize ==
ReadOperationCount ==
WriteOperationCount ==
PageFaults ==
OtherOperationCount ==
Handles ==
VM ==
WS ==
Path ==
Caption ==
CreationClassName ==
CreationDate ==
CSCreationClassName ==
CSName ==
Description ==
OtherTransferCount ==
CommandLine ==
ExecutionState ==
Handle ==
HandleCount ==
InstallDate ==
KernelModeTime ==
MaximumWorkingSetSize ==
MinimumWorkingSetSize ==
Name ==
OSCreationClassName ==
OSName ==

PowerShell 技能连载 - 将 Word 文档从 .doc 格式转为 .docx 格式(第 2 部分)

将旧式的 Word 文档转为新的 .docx 格式需要比较多工作量。多谢 PowerShell,您可以自动完成该转换工作:

但是,要做到这一点,有许多额外的步骤。如果要遵守安全指南,您需要了解文档中是否存在宏,并相应地更改扩展名。此外,如果文档处于只读模式,则无法转换该文档,并应跳过转换。如果你批量转换很多文档,有个进度条会很好。

感谢 Wpperal 市的安全专家 Lars Köpcke,增加了只读文档的宏观检查和测试!

以下是一个修订过的函数,能够神奇地批量转换。不过这仍然是一个原型。若果您希望在生产环境下使用它,请确保您理解它并且加入了所有需要的错误处理和报告。如果您不希望该函数重写原有的文件,请增加检测环节。

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
function Convert-WordDocument
{
param
(
# accept path strings or items from Get-ChildItem
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[string]
[Alias('FullName')]
$Path
)

begin
{
# we are collecting all paths first
[Collections.ArrayList]$collector = @()
}

process
{
# find extension
$extension = [System.IO.Path]::GetExtension($Path)

# we only process .doc and .dot files
if ($extension -eq '.doc' -or $extension -eq '.dot')
{
# add to list for later processing
$null = $collector.Add($Path)

}
}
end
{
# pipeline is done, now we can start converting!

Write-Progress -Activity Converting -Status 'Launching Application'

# initialize Word (must be installed)
$word = New-Object -ComObject Word.Application

$counter = 0
Foreach ($Path in $collector)
{
# increment a counter for the progress bar
$counter++

# open document in Word
$doc = $word.Documents.Open($Path)

# determine target document type
# if the doc has macros, use different extensions

[string]$targetExtension = ''
[int]$targetConversion = 0

switch ([System.IO.Path]::GetExtension($Path))
{
'.doc' {
if ($doc.HasVBProject -eq $true)
{
$targetExtension = '.docm'
$targetConversion = 13
}
else
{
$targetExtension = '.docx'
$targetConversion = 16
}
}
'.dot' {
if ($doc.HasVBProject -eq $true)
{
$targetExtension = '.dotm'
$targetConversion = 15
}
else
{
$targetExtension = '.dotx'
$targetConversion = 14
}
}
}

# conversion cannot work for read-only docs
If (!$doc.ActiveWindow.View.ReadingLayout)
{
if ($targetConversion -gt 0)
{
$pathOut = [IO.Path]::ChangeExtension($Path, $targetExtension)

$doc.Convert()
$percent = $counter * 100 / $collector.Count
Write-Progress -Activity 'Converting' -Status $pathOut -PercentComplete $percent
$doc.SaveAs([ref]$PathOut,[ref] $targetConversion)
}
}

$word.ActiveDocument.Close()
}

# quit Word when done
Write-Progress -Activity Converting -Status Done.
$word.Quit()
}
}

以下是调用示例:

1
PS> dir F:\documents -Include *.doc, *.dot -Recurse | Convert-WordDocument

PowerShell 技能连载 - 将 Word 文档从 .doc 格式转为 .docx 格式(第 1 部分)

将旧式的 Word 文档转为新的 .docx 格式需要比较多工作量。多谢 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
function Convert-WordDocument
{
param
(
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[string]
[Alias('FullName')]
$Path
)
begin
{
# launch Word
$word = New-Object -ComObject Word.Application
}

process
{
# determine target path
$pathOut = [System.IO.Path]::ChangeExtension($Path, '.docx')

# open the document
$doc = $word.Documents.Open($Path)

Write-Progress -Activity 'Converting' -Status $PathOut

# important: do the actual conversion
$doc.Convert()

# save in the appropriate format
$doc.SaveAs([ref]$PathOut,[ref]16)

# close document
$word.ActiveDocument.Close()
}
end
{
# close word
$word.Quit()
}
}

PowerShell 技能连载 - 探索 PowerShell 模块

大多数 PowerShell 的命令都存在于模块中,通过增加新的模块,就可以将新的命令添加到 PowerShell 环境中。要查找一个命令是否位于某个模块中,请使用 Get-Command 命令。下一行代码返回发布 Get-Service 命令的模块:

1
PS C:\> Get-Command -Name Get-Service | Select-Object -ExpandProperty Module

如果 Module 属性是空值,那么改命令不是通过一个模块发布的。对于所有非 PowerShell 命令,例如应用程序,都是这个情况:

1
PS C:\> Get-Command -Name notepad | Select-Object -ExpandProperty Module

下一步,让我们列出您系统中所有可用的模块:

1
PS> Get-Module -ListAvailable | Select-Object -Property Name, Path

如果更深入地查看这些结果,您会注意到 Get-Module 列出多于一个文件夹。PowerShell 指定的缺省模块文件夹是以分号分隔的列表形式存储在 $env:PSModulePath 环境变量中,类似应用程序的 $env:Path

PS> $env:PSModulePath
C:\Users\tobia\OneDrive\Dokumente\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:
\Windows\system32\WindowsPowerShell\v1.0\Modules

PS> $env:PSModulePath -split ';'
C:\Users\tobia\OneDrive\Dokumente\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules

如果您希望探索一个指定模块的内容,请查看 Path 属性:它通常指向一个 .psd1 文件,其中包含模块的元数据。它是指定模块版本和版权信息的地方,并且通常它的 RootModule 入口指定了模块的代码。如果这是一个使用 PowerShell 代码构建的模块,那么它是一个 .psm1 文件,否则是一个二进制的 DLL 文件。

要检查模块内容,请在 Windows 资源管理器中打开它的父文件夹。例如,下面一行代码在 Windows 资源管理器中打开 “PrintManagement” 模块(假设它存在于您的机器中):

1
2
3
4
PS> Get-Module -Name PrintManagement -ListAvailable | Select-Object -ExpandProperty Path | Split-Path
C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PrintManagement

PS> explorer (Get-Module -Name PrintManagement -ListAvailable | Select-Object -ExpandProperty Path | Split-Path)

这个快速的演练解释了为什么 PowerShell 没有固定的命令集,以及为什么给定命令在一个系统上可用并且在另一个系统上不可用。新模块可以由操作系统(例如 Windows 10 附带的模块多于 Windows 7)、已安装的软件(例如 SQLServer)、已激活的角色(例如域控制器)引入,并且模块也可以手动安装。

例如这行代码从公开的 PowerShell Gallery 中安装一个免费的模块,并且添加了用于创建各种二维码的新命令:

1
PS C:\> Install-Module -Name QRCodeGenerator -Scope CurrentUser -Force

一旦模块安装完毕,您会得到类似这样的命令,用来创建 Twitter 用户数据文件的二维码:

1
PS C:\> New-QRCodeTwitter -ProfileName tobiaspsp -Show

要查看某个模块中所有命令,请试试这行代码:

1
2
3
4
5
6
7
8
PS C:\> Get-Command -Module QRCodeGenerator

CommandType Name Version Source
----------- ---- ------- ------
Function New-QRCodeGeolocation 1.2 QRCodeGenerator
Function New-QRCodeTwitter 1.2 QRCodeGenerator
Function New-QRCodeVCard 1.2 QRCodeGenerator
Function New-QRCodeWifiAccess 1.2 QRCodeGenerator

PowerShell 技能连载 - WMI 浏览器

WMI(Windows管理规范)是一个很好的信息来源:几乎可以在其中找到有关计算机的任何信息。困难的部分不是 WMI 查询本身。困难的部分是找出适当的 WMI 类名称和属性:

例如,要获取您 BIOS 的信息,请运行以下代码:

1
2
3
4
5
6
7
8
PS> Get-CimInstance -ClassName Win32_BIOS


SMBIOSBIOSVersion : 1.0.9
Manufacturer : Dell Inc.
Name : 1.0.9
SerialNumber : 4ZKM0Z2
Version : DELL - 20170001

获取其他信息只需要替换您要查询的类名。因此,例如要获取有关您的操作系统的信息,请运行以下命令:

1
2
3
4
5
6
7
8
9
PS> Get-CimInstance -ClassName Win32_OperatingSystem


SystemDirectory : C:\Windows\system32
Organization :
BuildNumber : 18362
RegisteredUser : tobias.weltner@email.de
SerialNumber : 00330-50000-00000-AAOEM
Version : 10.0.18362

为了找到任何信息并找到合适的 WMI 类名称,我们创建了一个智能的 “WMI Explorer“,它实际上只是一个简单的功能。它需要Windows PowerShell (PowerShell 3-5):

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
function Find-WmiClass
{
# show all properties, not just 4
$oldLimit = $FormatEnumerationLimit
$global:FormatEnumerationLimit = -1
# list all WMI classes...
Get-WmiObject -Class * -List |
# ...with at least 4 properties
Where-Object { $_.Properties.Count -gt 4 } |
# let the user select one
Out-GridView -Title 'Select a class that seems interesting' -OutputMode Single |
ForEach-Object {
# query the selected class
$Name = $_.name
$props = Get-WmiObject -Class $Name |
# take the first instance
Select-Object -Property * -First 1 |
ForEach-Object {
# turn the object into a hash table, and exclude empty properties
$_ | & {
$_.PSObject.Properties |
Sort-Object -Property Name |
Where-Object { $_.Value -ne $null } |
ForEach-Object {
$hashtable = [Ordered]@{}} { $hashtable[$_.Name] = $_.Value } { $hashtable }
} |
# show the properties and let the user select
Out-GridView -Title "$name : Select all properties you need (hold CTRL)" -PassThru |
ForEach-Object {
# return the selected property names
$_.Name
}
}
# take all selected properties
$prop = $props -join ', '
# create the command for it:
$a = "Get-CimInstance -Class $Name | Select-Object -Property $prop"
# place it into the clipboard
$a | Set-Clipboard
Write-Warning "Command is also available from the clipboard"
$a
}
# reset format limit
$global:FormatEnumerationLimit = $oldLimit
}

以下是它的工作原理:

  • 运行上面的代码,将添加一个名为 Find-WmiClass 的新命令
  • 运行 Find-WmiClass 以执行该函数
  • 将打开一个网格视图窗口,并显示标准命名空间 root\cimv2 中所有可用的 WMI 类(如果要搜索其他命名空间,请调整代码并将`-Namespace 参数添加到 Get-WmiObject 调用中)
  • 现在,在网格视图窗口的顶部文本字段中输入您要查找的内容,它就像一个过滤器。例如,如果输入 UserName,则网格视图会将列表限制为名称或任何属性名称中任何位置具有 “UserName” 的所有类。过滤后只有几个类可供选择。
  • 选择要调查的类,例如 “Win32_ComputerSystem”,然后单击网格右下角的“确定”按钮。
  • 现在查询选定的类,并且它的第一个实例显示在另一个网格视图中。每个属性都显示在其自己的行上,因此您可以再次过滤显示。按住 CTRL 键选择要保留的所有属性。然后再次单击确定。
  • 将在控制台中显示创建的命令。它还位于剪贴板中。因此要测试命令的话,请按 CTRL + V,然后按 Enter。

感谢 Find-WmiClass 命令,探索 WMI 并找到有用的 WMI 类和属性变得非常容易。