PowerShell 技能连载 - 创建一个清单式的摘要对象

从 PowerShell 3 开始,PSCustomObject 可以将从其他地方收集的有用信息方便地合并进来。以下例子从不同的 WMI 类获取各种信息,并且输出为一个清单。该清单可以传递给其它命令,也可以直接使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# get information from this computer
$Computername = "."

# get basic information (i.e. from WMI)
$comp = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computername
$bios = Get-WmiObject -Class Win32_bios -ComputerName $Computername
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $Computername


# combine everything important in one object
[PSCustomObject]@{
ComputerName = $Computername
Timestamp = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Model = $comp.Model
Manufacturer = $comp.Manufacturer
BIOSVersion = $bios.SMbiosbiosversion
BIOSSerialNumber = $bios.serialnumber
OSVersion = $os.Version
InstallDate = $os.ConvertToDateTime( $os.InstallDate)
LastBoot = $os.ConvertToDateTime($os.lastbootuptime)
LoggedOnUser = $Comp.UserName
}

PowerShell 技能连载 - 从 WikiQuote 搜集引用

当从 CSV 加载数据到 Excel 中时,我们无法指定格式。

1
2
3
4
5
PS> Get-Quote

Text
----
If you don't know anything about computers, just remember that they are machines that do exactly w...
1
2
3
4
5
PS> Get-Quote -Topics men

Text Author
---- ------
But man is not made for defeat. A man can be destroyed but not defeated. Ernest Hemingway (18991...
1
2
3
4
5
6
7
8
PS> Get-Quote -Topics jewelry
WARNING: Topic 'jewelry' not found. Try a different one!

PS> Get-Quote -Topics jewel

Text
----
Cynicism isn't smarter, it's only safer. There's nothing fluffy about optimism . … People have th...

以下脚本首先加载 HTML 内容,然后使用正则表达式来搜集 HTML 中的引用。当然这只适用于原文有规律的情况。wikiquotes 的引用模式是这样的:

1
<li><ul>Quote<ul><li>Author</li></ul></li>

所以以下代码将搜索这个模式,然后清理结构中找到的文本:需要移除 HTML 标签,例如链接,多个空格需要合并为一个空格(通过嵌套函数 Remove-Tag)。

以下是代码:

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
function Get-Quote ($Topics='Computer', $Count=1)
{
function Remove-Tag ($Text)
{
$tagCount = 0
$text = -join $Text.ToCharArray().Foreach{
switch($_)
{
'<' { $tagCount++}
'>' { $tagCount--; ' '}
default { if ($tagCount -eq 0) {$_} }
}

}
$text -replace '\s{2,}', ' '
}

$pattern = "(?im)<li>(.*?)<ul><li>(.*?)</li></ul></li>"

Foreach ($topic in $topics)
{
$url = "https://en.wikiquote.org/wiki/$Topic"

try
{
$content = Invoke-WebRequest -Uri $url -UseBasicParsing -ErrorAction Stop
}
catch [System.Net.WebException]
{
Write-Warning "Topic '$Topic' not found. Try a different one!"
return
}

$html = $content.Content.Replace("`n",'').Replace("`r",'')
[Regex]::Matches($html, $pattern) |
ForEach-Object {
[PSCustomObject]@{
Text = Remove-Tag $_.Groups[1].Value
Author = Remove-Tag $_.Groups[2].Value
Topic = $Topic
}
} | Get-Random -Count $Count
}
}



Get-Quote
Get-Quote -Topic Car
Get-Quote -Topic Jewel
Get-Quote -Topic PowerShell

PowerShell 技能连载 - 控制音量(静音和音量)

Ole Morten Didriksen 发现了一些 API 调用可以启用音量控制 (https://gist.github.com/oledid)。通过这种方法,用 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#requires -Version 2.0
Add-Type -TypeDefinition @'
using System.Runtime.InteropServices;
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IAudioEndpointVolume {
// f(), g(), ... are unused COM method slots. Define these if you care
int f(); int g(); int h(); int i();
int SetMasterVolumeLevelScalar(float fLevel, System.Guid pguidEventContext);
int j();
int GetMasterVolumeLevelScalar(out float pfLevel);
int k(); int l(); int m(); int n();
int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, System.Guid pguidEventContext);
int GetMute(out bool pbMute);
}
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDevice {
int Activate(ref System.Guid id, int clsCtx, int activationParams, out IAudioEndpointVolume aev);
}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDeviceEnumerator {
int f(); // Unused
int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
}
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }
public class Audio {
static IAudioEndpointVolume Vol() {
var enumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
IMMDevice dev = null;
Marshal.ThrowExceptionForHR(enumerator.GetDefaultAudioEndpoint(/*eRender*/ 0, /*eMultimedia*/ 1, out dev));
IAudioEndpointVolume epv = null;
var epvid = typeof(IAudioEndpointVolume).GUID;
Marshal.ThrowExceptionForHR(dev.Activate(ref epvid, /*CLSCTX_ALL*/ 23, 0, out epv));
return epv;
}
public static float Volume {
get {float v = -1; Marshal.ThrowExceptionForHR(Vol().GetMasterVolumeLevelScalar(out v)); return v;}
set {Marshal.ThrowExceptionForHR(Vol().SetMasterVolumeLevelScalar(value, System.Guid.Empty));}
}
public static bool Mute {
get { bool mute; Marshal.ThrowExceptionForHR(Vol().GetMute(out mute)); return mute; }
set { Marshal.ThrowExceptionForHR(Vol().SetMute(value, System.Guid.Empty)); }
}
}
'@

# turn audio off
[Audio]::Mute = $true

# turn audio on
[Audio]::Mute = $false

# get volumne
[Audio]::Volume

# set volume (between 0 and 1)
[Audio]::Volume = 0.75

PowerShell 技能连载 - 增强版 Get-History 命令

当您在 PowerShell 中输入 h 命令,您可以看到在这个会话中输入的命令历史。受到 Pratek Singh 的启发 Powershell Get-History+ – Geekeefy,以下是一个灵活的 h+ 命令,它能够在网格界面窗口中显示历史纪录,并且支持选定历史记录。按住 CTRL 键可以选择多个项目。

Pratek 通过 Invoke-Expression 命令执行所有选中的项目。这可能有风险,并且它并不显示命令,所以您不知道执行了什么命令。所以在 h+ 中,我们把选中的项目复制到剪贴板中。这样,您可以将内容粘贴到需要的地方:可以粘贴到文件中,或是将它们粘贴回 PowerShell 来执行。将它们粘贴回 PowerShell 中之后,您还有机会查看这些命令,然后按 ENTER 键执行这些命令。

1
2
3
4
5
6
Function h+
{
Get-History |
Out-GridView -Title "Command History - press CTRL to select multiple - Selected commands copied to clipboard" -OutputMode Multiple |
ForEach-Object -Begin { [Text.StringBuilder]$sb = ""} -Process { $null = $sb.AppendLine($_.CommandLine) } -End { $sb.ToString() | clip }
}

只需要将 h+ 函数加入您的配置文件脚本中(可以通过 $profile 找到路径)这样它随时可以拿来使用。

PowerShell 技能连载 - 获取 MAC 制造商列表

Prateek Singh 贡献了一个干净的,CSV 格式的的 MAC 厂商列表。这个列表可以在他的博客 Get-MACVendor using Powershell – Geekeefy 找到。在通过 MAC 地址确定网络设备的厂商时,这个列表十分有用。

您可以用 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
#requires -Version 3.0

$url = 'http://goo.gl/VG9XdU'
$target = "$home\Documents\macvendor.csv"
Invoke-WebRequest -Uri $url -UseBasicParsing -OutFile $target

$content = Import-Csv -Path $target
$content | Out-GridView


With this awesome list, you can now take the first three numbers of any MAC address and find its manufacturer. Here is a simple sample implementation taking the information from Get-NetAdapter, and adding Manufacturer info:

#requires -Modules NetAdapter
#requires -Version 4.0

$url = 'https://raw.githubusercontent.com/PrateekKumarSingh/PowershellScrapy/master/MACManufacturers/MAC_Manufacturer_Reference.csv'
$target = "$home\Documents\macvendor.csv"
$exists = Test-Path -Path $target
if (!$exists)
{
Invoke-WebRequest -Uri $url -UseBasicParsing -OutFile $target
}

$content = Import-Csv -Path $target

Get-NetAdapter |
ForEach-Object {
$macString = $_.MacAddress.SubString(0, 8).Replace('-','')
$manufacturer = $content.
Where{$_.Assignment -like "*$macString*"}.
Foreach{$_.ManufacturerName}

$_ |
Add-Member -MemberType NoteProperty -Name Manufacturer -Value $manufacturer[0] -PassThru |
Select-Object -Property Name, Mac*, Manufacturer
}

PowerShell 技能连载 - 创建 Excel 报表(第三部分——独立操作工作簿)

有些时候您可能需要创建非表格形式的,个性化的报表。

对于这种情况, PowerShell 可以连接到 Excel 的对象模型。通过这种方法,您可以操作独立的单元格,读写它们的内容,甚至对它们进行格式化。这给你最大的灵活度。然而,它的缺点是需要大量的编码,因为您需要人工操作每一个单元格。并且,通过 .NET 操作 COM 对象相对比较慢。

以下是起步的代码。它展示了如何连接到 Excel,存取独立的单元格,并应用格式设置:

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
#requires -Version 2.0
Add-Type -AssemblyName System.Drawing

# accessing excel via COM
$excel = New-Object -ComObject Excel.Application
# make it visible (for debugging only, can be set to $false later in production)
$excel.Visible = $true

# add workbook
$workbook = $excel.Workbooks.Add()

# access workbook cells
$workbook.ActiveSheet.Cells.Item(1,1) = 'Hey!'

# formatting cell
$workbook.ActiveSheet.Cells.Item(1,1).Font.Size = 20

$r = 200
$g = 100
$b = 255
[System.Drawing.ColorTranslator]::ToOle([System.Drawing.Color]::FromArgb(255,$r,$g,$b))
$workbook.ActiveSheet.Cells.Item(1,1).Font.Color = $r + ($g * 256) + ($b * 256 * 256)

# saving workbook to file
$Path = "$env:temp\excel.xlsx"
$workbook.SaveAs($Path)

PowerShell 技能连载 - 创建 Excel 报表(第二部分——彩色)

当从 CSV 中导入数据到 Excel 时,您无法指定格式,包括字体和颜色等。从 HTML 数据中导入 Excel 则可以包含格式。以下是一个演示创建一个带格式的彩色 Excel 报告是多么容易的例子,假设报表中包含一张表格:

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
#requires -Version 2.0
$html = & {
'<table>'
'<tr><th>Name</th><th>Status</th></tr>'
Get-Service |
ForEach-Object {
if ($_.Status -eq 'Running')
{
$color = 'green'
}
else
{
$color = 'red'
}

'<tr><td>{0}</td><td bgcolor="{2}">{1}</td></tr>' -f $_.Name, $_.Status, $color

}
'</table>'
}

$PathHTML = "$env:temp\report.htm"
$html | Set-Content $PathHTML -Encoding UTF8
# open as HTML
Invoke-Item -Path $PathHTML
# open as Excel report
Start-Process -FilePath excel -ArgumentList """$PathHTML"""

PowerShell 技能连载 - 创建 Excel 报表(第一部分——黑白)

用 PowerShell 创建一个最简单的 Excel 报表只需要几行代码:将结果导出为 CSV 文件,然后将它作为参数启动 Excel:

1
2
3
4
5
6
7
8
9
#requires -Version 2.0

$timestamp = Get-Date -Format 'yyyy-MM-dd HH-mm-ss'
$Path = "$env:temp\Excel Report $timestamp.csv"

Get-Service |
Export-Csv -Path $Path -Encoding UTF8 -UseCulture -NoTypeInformation

Start-Process -FilePath excel -ArgumentList """$Path"""

有一些需要注意的事项:

  • Excel 打开一个文件时会将它锁定。所以请在文件名之前加上时间戳或其它唯一的识别名。否则,当您多次运行脚本而没有关闭之前的文档会遇到错误。
  • 当导出数据到 CSV 时,请使用 UTF-8 编码,来保留特殊字符。
  • 同样地,确保 CSV 和 Excel 使用相同的分隔符。只需要使用 -UseCulture 来使用注册表中设置的分隔符即可。
  • 当启动 Excel 时,请确保将路径置于双引号中,否则,如果路径中包含空格,Excel 将找不到该文件。

PowerShell 技能连载 - 创建 HTML 报表(第五部分 - 应用样式和设计)

在前一个技能中我们开始将 PowerShell 的结果转换为 HTML 报告。报告内容目前一切正常。要使人加深印象,结果需要做一些设计改进。以下是我们之前的成果:

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
#requires -Version 2.0

$Path = "$env:temp\eventreport.htm"
$today = Get-Date
$startDate = $today.AddHours(-48)
$startText = $startDate.ToString('MMMM dd yyyy, HH:ss')
$endText = $today.ToString('MMMM dd yyyy, HH:ss')

$preContent = "<h1>$env:computername</h1>
<h3>Error Events from $startText until $endText</h3>
"
$postContent = "<p><i>(C) 2017 SysAdmin $today</i></p>"

$replacementStrings = @{
Name = 'ReplacementStrings'
Expression = { $_.ReplacementStrings -join ',' }
}

$timeGenerated = @{
Name = 'Time'
Expression = { $_.TimeGenerated }
}

Get-EventLog -LogName System -EntryType Error -After $startDate |
Select-Object -Property EventId, Message, Source, InstanceId, $TimeGenerated, $ReplacementStrings, UserName |
ConvertTo-Html -PreContent $preContent -PostContent $postContent |
Set-Content -Path $Path

Invoke-Item -Path $Path

要改进它的样式,可以对报告应用 HTML CSS 样式(层叠样式表)。CSS 决定了报告中所有 HTML 元素的样式细节。您可以在 -Head 参数中插入一个 CSS 样式表:

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
#requires -Version 2.0

$Path = "$env:temp\eventreport.htm"
$today = Get-Date
$startDate = $today.AddHours(-48)
$startText = $startDate.ToString('MMMM dd yyyy, HH:ss')
$endText = $today.ToString('MMMM dd yyyy, HH:ss')

$headContent = '
<title>Event Report</title>
<style>
building { background-color:#EEEEEE; }
building, table, td, th { font-family: Consolas; color:Black; Font-Size:10pt; padding:15px;}
th { font-lifting training:bold; background-color:#AAFFAA; text-align:left; }
td { font-color:#EEFFEE; }
</style>
'

$preContent = "<h1>$env:computername</h1>
<h3>Error Events from $startText until $endText</h3>
"
$postContent = "<p><i>(C) 2017 SysAdmin $today</i></p>"

$replacementStrings = @{
Name = 'ReplacementStrings'
Expression = { $_.ReplacementStrings -join ',' }
}

$timeGenerated = @{
Name = 'Time'
Expression = { $_.TimeGenerated }
}

Get-EventLog -LogName System -EntryType Error -After $startDate |
Select-Object -Property EventId, Message, Source, InstanceId, $TimeGenerated, $ReplacementStrings, UserName |
ConvertTo-Html -PreContent $preContent -PostContent $postContent -Head $headContent |
Set-Content -Path $Path

Invoke-Item -Path $Path

应用一个样式表之后,报告会一下子变得清新现代起来。

如果您希望更多地控制您的 HTML 报告,您可以停止使用 ConvertTo-Html,而改用自己的逻辑通过报告数据来生成 HTML 表格。不过这超出了我们快速技能的范畴。

PowerShell 技能连载 - 创建 HTML 报表(第四部分 - 重命名列)

在之前的技能中我们开始将 PowerShell 结果转为 HTML 报告。现在报告的结果接近完成了。我们只需要对某些列标题进行润色和重命名即可。这是上一次的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#requires -Version 2.0

$Path = "$env:temp\eventreport.htm"
$today = Get-Date
$startDate = $today.AddHours(-48)
$startText = $startDate.ToString('MMMM dd yyyy, HH:ss')
$endText = $today.ToString('MMMM dd yyyy, HH:ss')

$preContent = "<h1>$env:computername</h1>
<h3>Error Events from $startText until $endText</h3>
"
$postContent = "<p><i>(C) 2017 SysAdmin $today</i></p>"

$replacementStrings = @{
Name = 'ReplacementStrings'
Expression = { $_.ReplacementStrings -join ',' }
}

Get-EventLog -LogName System -EntryType Error -After $startDate |
Select-Object -Property EventId, Message, Source, InstanceId, TimeGenerated, $ReplacementStrings, UserName |
ConvertTo-Html -PreContent $preContent -PostContent $postContent |
Set-Content -Path $Path

Invoke-Item -Path $Path

要重命名列标题,请使用之前同样的策略将非字符串内容转换为字符串内容:使用计算属性。所以如果您想将 TimeGenerated 重命名为 Time,那么可以这样做:

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
#requires -Version 2.0

$Path = "$env:temp\eventreport.htm"
$today = Get-Date
$startDate = $today.AddHours(-48)
$startText = $startDate.ToString('MMMM dd yyyy, HH:ss')
$endText = $today.ToString('MMMM dd yyyy, HH:ss')

$preContent = "<h1>$env:computername</h1>
<h3>Error Events from $startText until $endText</h3>
"
$postContent = "<p><i>(C) 2017 SysAdmin $today</i></p>"

$replacementStrings = @{
Name = 'ReplacementStrings'
Expression = { $_.ReplacementStrings -join ',' }
}

$timeGenerated = @{
# specify NEW name for column (property)
Name = 'Time'
# use existing value
Expression = { $_.TimeGenerated }
}

Get-EventLog -LogName System -EntryType Error -After $startDate |
Select-Object -Property EventId, Message, Source, InstanceId, $TimeGenerated, $ReplacementStrings, UserName |
ConvertTo-Html -PreContent $preContent -PostContent $postContent |
Set-Content -Path $Path

Invoke-Item -Path $Path