PowerShell 技能连载 - 在资源管理器中启用预览 PowerShell 文件

当您在 Windows 的资源管理器中打开预览窗格查看 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
function Enable-PowerShellFilePreview
{
[CmdletBinding()]
param
(
[string]
$Font = 'Courier New',

[int]
$FontSize = 60
)

# set the font and size (also applies to Notepad)
$path = "HKCU:\Software\Microsoft\Notepad"
Set-ItemProperty -Path $path -Name lfFaceName -Value $Font
Set-ItemProperty -Path $path -Name iPointSize -Value $FontSize

# enable the preview of PowerShell files
$path = 'HKCU:\Software\Classes\.ps1'
$exists = Test-Path -Path $path
if (!$exists){
$null = New-Item -Path $Path
}
$path = 'HKCU:\Software\Classes\.psd1'
$exists = Test-Path -Path $path
if (!$exists){
$null = New-Item -Path $Path
}

$path = 'HKCU:\Software\Classes\.psm1'
$exists = Test-Path -Path $path
if (!$exists){
$null = New-Item -Path $Path
}


Get-Item HKCU:\Software\Classes\* -Include .ps1,.psm1,.psd1 | Set-ItemProperty -Name PerceivedType -Value text
}

运行这个函数后,使用这个命令:

1
PS> Enable-PowerShellFilePreview

如果您喜欢的话,还可以改变预览的字体系列和字号。请注意该设置和记事本共享:

1
PS> Enable-PowerShellFilePreview -Font Consolas -FontSize 100

不需要重启系统就可以生效。只需要确保 Windows 资源管理器的预览窗格可见,并选取一个 PowerShell 文件。

PowerShell 技能连载 - 移除空的数组元素(第 2 部分)

如果您想彻底移除空的数组元素(而不需要关心任何空属性),以下是一些性能根本不同的几种实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
# create huge array with empty elements
$array = 1,2,3,$null,5,0,3,1,$null,'',3,0,1
$array = $array * 1000

# "traditional" approach (6 sec)
Measure-Command {
$newArray2 = $array | Where-Object { ![string]::IsNullOrWhiteSpace($_) }
}

# smart approach (0.03 sec)
Measure-Command {
$newArray3 = foreach ($_ in $array) { if (![String]::IsNullOrWhiteSpace($_)){ $_} }
}

PowerShell 技能连载 - 移除空的数组元素(第 1 部分)

有些时候您会遇到包含空元素的列表(数组)。那么移除空元素的最佳方法是?

让我们首先关注一个普遍的场景:以下代码从注册表读取已安装的软件并创建一个软件清单。该软件清单将显示在一个网格视图窗口中,而很可能能看到包含空属性的元素:

1
2
3
4
5
6
7
8
9
$Paths = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKCU:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'

$software = Get-ItemProperty -Path $paths -ErrorAction Ignore |
Select-Object -Property DisplayName, DisplayVersion, UninstallString

$software | Out-GridView

让我们忽略所有显示名称为空的元素:

1
2
# remove elements with empty DisplayName property
$software = $software | Where-Object { [string]::IsNullOrWhiteSpace($_.DisplayName)}

由于空属性既包含“真正”为空 ($null) 也包含空字符串 (''),您需要检查它们两者。更简单的方法是将它们隐式转换为 Boolean。然而,这样做仍然会移除数值 0:

1
2
# remove elements with empty DisplayName property
$software = $software | Where-Object { $_.DisplayName }

使用 PowerShell 3 引入的简化语法,您甚至可以这样写:

1
2
# remove elements with empty DisplayName property
$software = $software | Where-Object DisplayName

如果你想节省几毫秒,请使用 where 方法:

1
2
# remove elements with empty DisplayName property
$software = $software.Where{ $_.DisplayName }

如果您想处理一个大数组,用 foreach 循环更有效(效率提升 15 倍):

1
2
# remove elements with empty DisplayName property
$software = foreach ($_ in $software){ if($_.DisplayName) { $_ }}

PowerShell 技能连载 - “危险的”比较

假设您希望排除某个数组中所有为空字符串或者 null 元素。以下是许多人可能的做法:

1
2
3
4
5
6
7
8
PS> 1,2,$null,"test","",9 | Where-Object { $_ -ne '' -and $_ -ne $null }

1
2
test
9

PS>

然而,这个对比是危险的,因为它也排除了数值 0:

1
2
3
4
5
6
7
8
PS> 1,2,0,$null,"test","",0,9 | Where-Object { $_ -ne '' -and $_ -ne $null }

1
2
test
9

PS>

PowerShell 过滤掉了数值 0,因为它等同于一个空字符串:

1
2
3
4
5
PS> 0 -eq ''
True

PS> 1 -eq ''
False

这是因为在比较时,以等号左侧的数据类型为准,而由于左侧是一个 integer 值,所以 PowerShell 将空字符串也转换成一个 integer,而转换的结果值是 0。

为了安全地进行比较,请记住一定将相关的数据类型放在等号左侧,而不是右侧:

1
2
3
4
5
6
7
8
9
10
PS> 1,2,0,$null,"test","",0,9 | Where-Object { '' -ne $_ -and $null -ne $_ }

1
2
0
test
0
9

PS>

或者更好一点,使用 API 函数来确认空值:

1
2
3
4
5
6
7
8
9
10
PS> 1,2,0,$null,"test","",0,9 | Where-Object { ![string]::IsNullOrWhiteSpace($_) }

1
2
0
test
0
9

PS>

PowerShell 技能连载 - 计算一个月的第一天和最后一天

对于报表以及类似的场景,脚本可能需要获得指定月份的第一天和最后一天。第一天很简单,但最后一天依赖于月份和年份。以下是一个简单的计算器。只需要指定您需要的月份和年份:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[ValidateRange(1,12)][int]$month = 3
$year = 2019
$last = [DateTime]::DaysInMonth($year, $month)
$first = Get-Date -Day 1 -Month $month -Year $year -Hour 0 -Minute 0 -Second 0
$last = Get-Date -Day $last -Month $month -Year $year -Hour 23 -Minute 59 -Second 59



PS> $first
3/1/2019 12:00:00 AM

PS> $last
3/31/2019 11:59:59 PM

PS>

PowerShell 技能连载 - 格式化 DateTime

当您拥有一个真正的 DateTime 对象(比如不是字符串)时,您就拥有了许多强大的格式化功能。您可以直接获取一个 DateTime 对象:

1
2
3
4
PS> $installDate = (Get-CimInstance -Class Win32_OperatingSystem).InstallDate

PS> $installDate.GetType().FullName
System.DateTime

或者您可以将一个字符串转换为一个 DateTime 对象:

1
2
3
4
PS> $psconf = Get-Date -Date '2019-06-04 09:00'

PS> $psconf.GetType().FullName
System.DateTime

当您拥有一个 DateTime 对象时,请使用 ToString() 方法并提供一个或两个参数。

第一个参数决定您希望使用日期的哪些部分,并使用这些占位符(大小写敏感!):

y       Year
M       Month
d       Day
H       Hour
m       Minute
s       Second
f       Millisecond

指定了越多占位符,就可以得到越多的细节:

1
2
3
4
5
6
7
8
9
10
PS> (Get-Date).ToString('dd')
30

PS> (Get-Date).ToString('ddd')
So

PS> (Get-Date).ToString('dddd')
Sonntag

PS>

(如您所见,PowerShell 使用的是缺省的语言,这个例子中使用的是德语)

要以 ISO 格式输出一个 DateTime,请使用这段代码:

1
2
3
4
5
6
PS> $installDate = (Get-CimInstance -Class Win32_OperatingSystem).InstallDate

PS> $installDate.ToString('yyyy-MM-dd HH:mm:ss')
2018-06-08 18:24:46

PS>

如果您也希望指定区域设置(语言),请在第二个参数指定 CultureInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> (Get-Date).ToString('dddd', [System.Globalization.CultureInfo]'en-us')
Sunday

PS> (Get-Date).ToString('dddd', [System.Globalization.CultureInfo]'zh')
星期日

PS> (Get-Date).ToString('dddd', [System.Globalization.CultureInfo]'es')
domingo

PS> (Get-Date).ToString('dddd', [System.Globalization.CultureInfo]'fr')
dimanche

PS>

如果您想了解某个区域设置的区域代码,请试试这段代码:

1
PS> [System.Globalization.CultureInfo]::GetCultures('Installed') | Out-GridView -PassThru

PowerShell 技能连载 - 解析 Windows 安装日期

是否关心过您的 Windows 已经安装了多久?一个单行的代码可以告诉您结果:

1
2
3
PS> (Get-CimInstance -Class Win32_OperatingSystem).InstallDate

Freitag, 8. Juni 2018 18:24:46

有两件事值得注意:第一,我们显然在使用德文的系统。第二,安装的日期可能比您想象的更近:每个新的 Windows 10 主版本更新实际上导致了一个完整的重新安装过程。

如果您希望改变 DateTime 输出的语言,只需要使用 ToString() 和一个 CultureInfo 对象:

1
2
3
4
PS> (Get-CimInstance -Class Win32_OperatingSystem).InstallDate.ToString([System.Globalization.CultureInfo]'en-us')
6/8/2018 6:24:46 PM

PS>

如果您想了解 Windows 安装了多少填,请使用 New-TimeSpan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PS> New-TimeSpan -Start (Get-CimInstance -Class Win32_OperatingSystem).InstallDate


Days : 204
Hours : 18
Minutes : 53
Seconds : 52
Milliseconds : 313
Ticks : 176936323133869
TotalDays : 204,787411034571
TotalHours : 4914,89786482969
TotalMinutes : 294893,871889782
TotalSeconds : 17693632,3133869
TotalMilliseconds : 17693632313,3869


PS> (New-TimeSpan -Start (Get-CimInstance -Class Win32_OperatingSystem).InstallDate).TotalDays
204,78764150864

PS> (New-TimeSpan -Start (Get-CimInstance -Class Win32_OperatingSystem).InstallDate).Days
204

PowerShell 技能连载 - 在文件管理器中隐藏 OneDrive

是否厌倦了 OneDrive 图表污染了您的文件管理器树形视图?如果您不使用 OneDrive,那么有两个很好用的函数可以在文件管理器里隐藏或显示 OneDrive 图标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Disable-OneDrive
{
$regkey1 = 'Registry::HKEY_CLASSES_ROOT\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}'
$regkey2 = 'Registry::HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}'
Set-ItemProperty -Path $regkey1, $regkey2 -Name System.IsPinnedToNameSpaceTree -Value 0
}


function Enable-OneDrive
{
$regkey1 = 'Registry::HKEY_CLASSES_ROOT\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}'
$regkey2 = 'Registry::HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}'
Set-ItemProperty -Path $regkey1, $regkey2 -Name System.IsPinnedToNameSpaceTree -Value 1
}

PowerShell 技能连载 - 将 Windows 服务器转变为工作站

PowerShell 5 及以上版本提供了一个自动添加 Windows 功能的 cmdlet,所以如果您正在运行 Windows Server 并且想使用 Workstation 功能,请以管理员权限打开一个 PowerShell,然后运行以下代码:

1
Enable-WindowsOptionalFeature -FeatureName DesktopExperience -All -Online -NoRestart

PowerShell 技能连载 - 自动打印到 XPS 文件

XPS 是由 Microsoft 开发的类似 PDF 的文档格式。虽然它并没有大规模使用,但它仍然是一种打印信息到文件的很好的内部格式。要无人值守地打印到 XPS 文件,首先您需要设置一个新的打印机,该打印机将自动打印到一个指定的输出文件:

1
2
3
4
5
6
#requires -RunAsAdministrator

$OutPath = "$env:temp\out.xps"
$PrinterName = "XPSPrinter"
Add-PrinterPort -Name $OutPath
Add-Printer -Name $PrinterName -DriverName 'Microsoft XPS Document Writer v4' -PortName $OutPath

请确保 XPS 查看器已经安装:

1
2
#requires -RunAsAdministrator
Enable-WindowsOptionalFeature -Online -FeatureName Xps-Foundation-Xps-Viewer -NoRestart

基于以上的准备工作,现在要将输出结果自动打印到 XPS 文件非常简单。以下是一个日常使用的打印函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Out-PrinterXPS ($Path = $(Read-Host -Prompt 'XPS document path to create'))
{
$PrinterName = "XPSPrinter"
$OutPath = "$env:temp\out.xps"

$exists = Test-Path -Path $OutPath
if ($exists)
{
Remove-Item -Path $OutPath
}

$input | Out-Printer -Name $PrinterName
do
{
Start-Sleep -Milliseconds 500
$exists = Test-Path -Path $OutPath
} while (!$exists)

Move-Item -Path $OutPath -Destination $Path -Force
}

让我们试试使用它!以下是一行在桌面上创建系统清单报告的代码:

1
2
3
4
5
6
7
8
# print to this file
$Path = "$home\desktop\inventar.xps"

# pipe the data to the file
systeminfo.exe /FO CSV | ConvertFrom-Csv | Out-PrinterXPS -Path $Path

# open the XPS file with the built-in viewer
Invoke-Item -Path $Path