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

PowerShell 技能连载 - 从 Unicode 文件中移除 BOM

BOM(字节顺序标记)是在某些 Unicode 编码的文本文件特有的字节顺序。如果您收到一个包含了 BOM 的文件,而要处理它的其它系统并不支持 BOM,那么以下是如何用 PowerShell 移除这类文件中的 BOM 的方法:

1
2
3
4
5
6
function Remove-BomFromFile ($OldPath, $NewPath)
{
$Content = Get-Content $OldPath -Raw
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[IO.File]::WriteAllLines($NewPath, $Content, $Utf8NoBomEncoding)
}

现在要获取一个文件的 BOM 并将它转为一个无 BOM 的文件就很方便了:

1
2
3
$Path = "$env:temp\export.csv"
$NewPath = "$env:temp\export_new.csv"
Remove-BomFromFile -OldPath $Path -NewPath $NewPath

PowerShell 技能连载 - PowerShell 速查表汇编(第 2 部分)

在前一个技能中我们提供了一个很棒的 PowerShell 速查表。让我们来看看 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
# enable SSL download
$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols

# download page
$url = "https://github.com/PrateekKumarSingh/CheatSheets/tree/master/Powershell"
$page = Invoke-WebRequest -UseBasicParsing -Uri $url
$links = $page.Links |
Where-Object { $_.href -like '*.pdf' } |
Select-Object -Property title, href |
# turn URLs into directly downloadable absolute URLs
ForEach-Object {
$_.href = 'https://github.com/PrateekKumarSingh/CheatSheets/raw/master/Powershell/' + $_.title
$_
}

# create folder on your desktop
$Path = "$home\Desktop\CheatSheets"
$exists = Test-Path -Path $Path
if (!$exists) { $null = New-Item -Path $path -ItemType Directory }

# download cheat sheets
$links | ForEach-Object {
$docPath = Join-Path -Path $Path -ChildPath $_.Title
Start-BitsTransfer -Source $_.href -Destination $docPath -Description $_.title
# alternate way of downloading
# Invoke-WebRequest -UseBasicParsing -Uri $_.href -OutFile $docPath
}

# open folder
explorer $Path

当您运行这段脚本时,PowerShell 将下载所有的速查表并且将它们存放在桌面上一个名为 “CheatSheets” 的新文件夹中。祝您读得愉快!

PowerShell 技能连载 - 获取文本文件编码

文本文件可以以不同的编码存储。需要正确地指定编码,才能正确地读取它们。这是为什么多数读取文本文件的 cmdlet 提供 -Encoding 参数(例如 Get-Content)。如果没有指定正确的编码,您可能会看到一堆乱码。

那么如何(自动地)确认某个指定的文本文件所使用的编码?以下是一个好用的函数:

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 Get-Encoding
{
param
(
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[Alias('FullName')]
[string]
$Path
)

process
{
$bom = New-Object -TypeName System.Byte[](4)

$file = New-Object System.IO.FileStream($Path, 'Open', 'Read')

$null = $file.Read($bom,0,4)
$file.Close()
$file.Dispose()

$enc = [Text.Encoding]::ASCII
if ($bom[0] -eq 0x2b -and $bom[1] -eq 0x2f -and $bom[2] -eq 0x76)
{ $enc = [Text.Encoding]::UTF7 }
if ($bom[0] -eq 0xff -and $bom[1] -eq 0xfe)
{ $enc = [Text.Encoding]::Unicode }
if ($bom[0] -eq 0xfe -and $bom[1] -eq 0xff)
{ $enc = [Text.Encoding]::BigEndianUnicode }
if ($bom[0] -eq 0x00 -and $bom[1] -eq 0x00 -and $bom[2] -eq 0xfe -and $bom[3] -eq 0xff)
{ $enc = [Text.Encoding]::UTF32}
if ($bom[0] -eq 0xef -and $bom[1] -eq 0xbb -and $bom[2] -eq 0xbf)
{ $enc = [Text.Encoding]::UTF8}

[PSCustomObject]@{
Encoding = $enc
Path = $Path
}
}
}

以下是一段检查您用户配置文件中所有文本文件的测试代码:

1
2
3
4
5
6
7
8
9
10
PS> dir $home -Filter *.txt -Recurse | Get-Encoding

Encoding Path
-------- ----
System.Text.UnicodeEncoding C:\Users\tobwe\E006_psconfeu2019.txt
System.Text.UnicodeEncoding C:\Users\tobwe\E009_psconfeu2019.txt
System.Text.UnicodeEncoding C:\Users\tobwe\E027_psconfeu2019.txt
System.Text.ASCIIEncoding C:\Users\tobwe\.nuget\packages\Aspose.Words\18.12.0\...
System.Text.ASCIIEncoding C:\Users\tobwe\.vscode\extensions\ms-vscode.powers...
System.Text.UTF8Encoding C:\Users\tobwe\.vscode\extensions\ms-vscode.powers...