PowerShell 技能连载 - 自动获取借口

Invoke-WebRequest 可以从网页获取 HTML 信息,并且可以用正则表达式来提取这些页面中的信息。

以下是一些可以获取英文的借口的代码:

1
2
3
4
5
6
7
8
9
$ProgressPreference = 'SilentlyContinue'

$url = "http://pages.cs.wisc.edu/~ballard/bofh/bofhserver.pl?$(Get-Random)"
$page = Invoke-WebRequest -Uri $url -UseBasicParsing
$pattern = '(?s)<br><font\ size\ =\ "\+2">(.{1,})</font'
if ($page.Content -match $pattern)
{
$matches[1].Trim() -replace '\n', '' -replace '\r', ''
}

以下代码将获取英语和德语混合的借口:

1
2
3
4
5
6
$page= Invoke-WebRequest "http://www.netzmafia.de/cgi-bin/bofhserver.cgi"
$pattern='(?s)<B>(.*?)</B>'
if ($page.Content -match $pattern)
{
$matches[1].Trim() -replace '\n', '' -replace '\r', ''
}

PowerShell 技能连载 - 在可扩展字符串中分隔变量

当使用双引号字符串时,您可以扩展它们当中的变量,类似这样:

1
2
PS C:\> "Windir: $env:windir"
Windir: C:\Windows

然而,没有明显的方法来标记变量的起止位置,所以以下操作将会失败:

1
2
PS C:\> "$env:windir: this is my Windows folder"
this is my Windows folder

解决方案是使用大括号来标识字符串内变量的起止位置:

1
2
PS C:\> "${env:windir}: this is my Windows folder"
C:\Windows: this is my Windows folder

PowerShell 技能连载 - 查看 Windows 生成号

当运行 winver.exe 时,您可以方便地获取到完整的 Windows 生成号。通过 PowerShell 读取生成号并不是那么明显。并没有内置的 cmdlet。

不过,要创建这样功能的函数很简单:

1
2
3
4
5
6
function Get-OSInfo
{
$path = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
Get-ItemProperty -Path $path -Name CurrentBuild, UBR, ReleaseID, CompositionEditionID |
Select-Object -Property CurrentBuild, UBR, ReleaseID, CompositionEditionID
}

结果看起来类似这样:

1
2
3
4
5
PS C:\> Get-OSInfo

CurrentBuild UBR ReleaseId CompositionEditionID
------------ --- --------- --------------------
15063 1088 1703 Enterprise

PowerShell 技能连载 - 检查 USB 设备

如果想知道某个特定的设备是否连接到您的计算机上,您可以使用 WMI 来提取所有即插即用设备的名称:

1
2
Get-WmiObject -Class Win32_PnpEntity |
Select-Object -ExpandProperty Caption

PowerShell 技能连载 - 使用 PSGraph

PSGraph 是一个非常棒的免费 PowerShell 库,您可以用它来将关系可视化。在使用 PSGraph 之前,需要安装它的依赖项(graphviz 引擎)。两者都需要管理员特权:

1
2
3
4
5
6
7
8
#requires -RunAsAdministrator

# install prerequisite (graphviz)
Register-PackageSource -Name Chocolatey -ProviderName Chocolatey -Location http://chocolatey.org/api/v2/
Find-Package graphviz | Install-Package -ForceBootstrap

# install PowerShell module
Install-Module -Name PSGraph

安装完成后,这是如何将对象关系可视化的代码:

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
$webServers = 'Web1','Web2','web3'
$apiServers = 'api1','api2'
$databaseServers = 'db1'

graph site1 {
# External/DMZ nodes
subgraph 0 -Attributes @{label='DMZ'} {
node 'loadbalancer' @{shape='house'}
rank $webServers
node $webServers @{shape='rect'}
edge 'loadbalancer' $webServers
}

subgraph 1 -Attributes @{label='Internal'} {
# Internal API servers
rank $apiServers
node $apiServers
edge $webServers -to $apiServers

# Database Servers
rank $databaseServers
node $databaseServers @{shape='octagon'}
edge $apiServers -to $databaseServers
}
} | Export-PSGraph -ShowGraph

这个例子中创建的图形使用脚本文件中的 hypothetical 服务器并向其添加合适的关系,并显示图像。

PowerShell 技能连载 - 使用 AD 过滤器配合 cmdlet(第 4 部分)

在前一个技能中,我们开始学习 ActiveDirectory 模块(免费的 RSAT 工具)中的 cmdlet 如何过滤执行结果,并且学习了如何合并过滤器表达式。今天我们将学习如何处理日期和时间。

有些 AD 属性包含日期和时间信息,例如上次登录的日期。这类信息是以一串非常长的 64 位整数标识的。您可以在 LDAP 过滤器中以这种格式使用日期和时间。

例如,要查找近 4 个星期中没有修改密码的所有用户:

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
$weeks = 4
# first, find out the AD time format from
# 4 weeks ago that will be used in the LDAPFilter

$today = Get-Date
# 4 weeks ago
$cutDate = $today.AddDays(-($weeks * 7))
# translate in AD time format
$cutDateAD = $cutDate.ToFileTimeUtc()

# next, find a way to convert back the AD file format
$realDate = @{
Name = 'Date'
Expression = { if ($_.pwdLastset -eq 0)
{
'[never]'
}
else
{
[DateTime]::FromFileTimeUtc($_.pwdLastset)
}
}

}

Get-ADUser -LDAPFilter "(pwdLastSet<=$cutDateAD)" -Properties pwdLastSet |
Select-Object -Property samaccountname, $realDate

实际上,当调用一个 DateTime 对象的 ToFileTimeUtc() 方法之后,将返回 AD 格式的数据。类似地,当您运行 [DateTime]::FromFileTimeUtc() 时,将会把 AD 格式转换为一个真实的 DateTime 对象。

PowerShell 技能连载 - 使用 AD 过滤器配合 cmdlet(第 3 部分)

在之前的技能中,我们开始学习 ActiveDirectory 模块(免费的 RSAT 工具)中的 cmdlet 如何过滤执行结果,并且开始以我们的方式插入快捷且完善的 LDAP 过滤器。

LDAP 过滤器有一个强制的需求。您必须使用原始的 ActiveDirectory 属性名,而不是许多 PowerShell cmdlet 中的友好名称。所以 “country” 需要改为 AD 的属性名 “co”。当您坚持使用这些名字后,创建 LDAP 过滤器十分容易。

这行代码将会从 Active Directory 中获取所有 Windows 10 计算机:

1
2
Get-ADComputer -LDAPFilter '(operatingSystem=*10*)' -Properties operatingSystem |
Select-Object samaccountname, operatingSystem

如果您想合并多个过滤器,请将它们加到小括号中,然后在前面添加 “&” 进行逻辑与操作,添加 “|” 进行逻辑或操作。所以这行代码查找所有从 Wuppertal 城市,名字以 “A” 开头的用户:

1
Get-ADUser -LDAPFilter '(&(l=Wuppertal)(name=a*))'

PowerShell 技能连载 - 使用 AD 过滤器配合 cmdlet(第 2 部分)

在前一个技能中,我们开始学习 ActiveDirectory 模块(免费的 RSAT 工具)中的 cmdlet 如何过滤执行结果。您学到了过滤器看起来像 PowerShell 代码,但是实际上不是。

对于简单的过滤,该过滤器工作正常。然而,当您使用操作符以外的 PowerShell 语言特性时,您很快会发现实际中使用的过滤器并不是 PowerShell 代码。

如果您想获得一个未配置文件路径的 AD 用户列表,您可能会好奇地尝试以下的代码:

1
2
Get-ADUser -Filter { profilePath -eq $null} -ResultSetSize 5
Get-ADUser -Filter { profilePath -eq ''} -ResultSetSize 5

两个过滤器都会失败。PowerShell 报告 $null 变量是未知的。而在第二行中,报告该该搜索查询不合法。

这是为什么大多数情况下使用在 Active Directory 常见的原生 LDAPFilters 是更方便(而且更快)的。LDAP 过滤器是括号中的表达式。它们包含一个名称和一个操作符。以下代码返回前 5 个有配置文件路径的用户:

1
Get-ADUser -LDAPFilter '(profilePath=*)' -ResultSetSize 5

加上 “!” 以后,可以将结果反转,所以以下代码将返回前 5 个没有配置文件路径的用户:

1
Get-ADUser -LDAPFilter '(!profilePath=*)' -ResultSetSize 5

以下代码将返回用户和他们的配置文件路径的列表:

1
2
Get-ADUser -LDAPFilter '(profilePath=*)' -Properties profilePath |
Select-Object samaccountName, profilePath

PowerShell 技能连载 - 使用 AD 过滤器配合 cmdlet(第 1 部分)

ActiveDirectory Powershell 模块中包含了免费的 RSAT 工具。您可以使用这个模块中的 cmdlet 来获取 AD 信息,例如用户或者组名。例如 Get-ADUserGet-ADComputer 等 cmdlet 支持服务端过滤器。不过,它们的工作方式可能和您设想的略有不同。

对于简单的查询,这些过滤器使用起来很容易。例如,这行代码筛选出前五个名字以 “A” 开头的用户,而且过滤器的语法看起来很像 PowerShell 代码:

1
Get-ADUser -Filter { name -like 'A*' } -ResultSetSize 5

不过它还不是真的 PowerShell 语法:-Filter 参数接受的是纯文本,所以您也可以使用引号代替大括号:

1
Get-ADUser -Filter " name -like 'A*' " -ResultSetSize 5

使用大括号(脚本块)仍是一个好主意,因为大括号包括的是 PowerShell 代码,所以在大括号内写代码时可以获得代码高亮和语法错误特性。脚本块稍后可以方便地转换为字符串(Get-ADUser 会自动处理)。

PowerShell 技能连载 - 创建临时文件名

当您需要写入信息到磁盘时,使用唯一的临时文件名是合理的。如果您使用静态的文件名并且多次运行您的代码,会一次又一次地覆盖写入同一个文件。如果某个人打开该文件并锁定它,将导致脚本执行失败。

以下是一些简单的生成唯一的临时文件名的方法:

1
2
3
4
5
6
7
8
9
10
11
# use a random number (slight chance of duplicates)
$path = "$env:temp\liste$(Get-Random).csv"
"Path is: $path"

# use a GUID. Guaranteed to be unique but somewhat hard on the human eye
$path = "$env:temp\liste$([Guid]::NewGuid().toString()).csv"
"Path is: $path"

# use timestamp with milliseconds
$path = "$env:temp\liste$(Get-Date -format yyyy-MM-dd_HHmmss_ffff).csv"
"Path is: $path"