PowerShell 技能连载 - 验证变量内容

从 PowerShell 5 开始,您可以为一个变量增加一个验证器。该验证器可以接受一个正则表达式,而且当您向该变量赋值时,该验证其检查新内容是否匹配正则表达式模式。如果不匹配,将会抛出一个异常,而该变量内容保持不变。

以下是一个存储 MAC 地址的变量示例:

1
[ValidatePattern('^[a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$')][string]$mac = '12:AB:02:33:12:22'

运行这段代码,然后尝试将一个新的 mac 地址赋值给该变量。如果新的 mac 地址不符合验证器,PowerShell 将抛出一个异常。

PowerShell 技能连载 - 使用 profile 脚本

默认情况下,PowerShell 在重新运行时会自动“忘记”某些设置。如果您需要“保持”设置,您需要使用 profile 脚本。当 PowerShell 在任何情况下启动时,会运行一段脚本。您可以向该脚本中添加任何命令。

这行代码将会打开当前的 PowerShell profile 脚本,而如果目前该脚本不存在,将会用 notepad 创建一个:

1
PS C:\> notepad $profile

您现在可以向 profile 脚本增加这段代码:

1
2
"Hello $env:username!"
Set-Alias -Name web -Value "$env:ProgramFiles\Internet Explorer\iexplore.exe"

您也可以运行这段代码来确保允许 PowerShell 执行本地脚本:

1
PS  C:\> Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force

下一次您运行 PowerShell 时,它将会向您致礼,并且创建一个新的名为 “web“ 的别名。该别名可以在 Internet Explorer 中打开网页:

1
PS C:\> web www.powershellmagazine.com

PowerShell 技能连载 - 优化命令自动完成

PowerShell 控制台(powershell.exe、pwsh.exe)提供了可扩展的自动完成支持。当您输入了一个空格和一个连字符,按下 TAB 键将循环切换可用的参数。

Windows PowerShell (powershell.exe) 与 Linux 和 macOS 的 PowerShell (pwsh.exe) 之间有一个巨大的差异。当您在后者中按下 TAB 键,它们将立即显示所有可用的选项,并且您可以选择一个需要的建议。

要为 powershell.exe 添加这个行为,只需要运行这行代码:

1
PS C:\> Set-PSReadlineOption -EditMode Emacs

下次当您输入参数的开头部分并按下 TAB 键时,您可以立即见到所有可用的选择。接下来,当重复输入这个命令时,您可以键入选择的首字母,按下 TAB 键即可完成选择。

这时候 Windows 用户可能会听到烦人的警告音。要关闭警告音,请运行:

1
PS C:\> Set-PSReadlineOption -BellStyle None

PowerShell 技能连载 - 远程读取配置表(第 2 部分)

在前一个例子中我们演示了使用早期的 DCOM 协议从远程读取另一台机器的注册表代码。

如果您可以使用 PowerShell 远程处理,情况变得更简单。您现在可以简单地编写在本机可运行的代码,然后将它“发射”到目标机器(支持多台):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# adjust this to a remote computer of your choice
# (or multiple computers, comma-separated)
# PowerShell remoting needs to be enabled on that computer
# and you need to have local Admin privileges on that computer
$ComputerName = 'pc01'

# execute this code remotely on the machine(s)
$code = {
# read the given registry value...
Get-ItemProperty -Path hklm:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\* |
# and show the reg values with the names below
Select-Object DisplayName, DisplayVersion, UninstallString
}

# "beam" the code over to the target computer(s), and retrieve
# the result, then show it in a grid view window
Invoke-Command -ScriptBlock $code -ComputerName $ComputerName |
Out-GridView

通过使用 PowerShell 远程处理,您只需要确保 PowerShell 远程处理的先决条件都具备。您可以用这条命令(需要本地管理员特权):

1
PS C:\> Enable-PSRemoting -SkipNetworkProfileCheck -Force

PowerShell 技能连载 - 远程读取配置表(第 1 部分)

如果您无法使用 PowerShell 远程处理,那么您需要通过 DCOM 从另一个系统中读取注册表值,以下是一些您可能希望试验的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ComputerName = 'pc01'
# NOTE: RemoteRegistry Service needs to run on a target system!
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $ComputerName)
$key = $reg.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall')

$key.GetSubKeyNames() | ForEach-Object {
$subkey = $key.OpenSubKey($_)
[PSCustomObject]@{
Name = $subkey.GetValue(‘DisplayName’)
Version = $subkey.GetValue(‘DisplayVersion’)
}
$subkey.Close()
}

$key.Close()
$reg.Close()

只需要做一些小改动,该代码就能返回一个 AD 使用的列表

这段示例代码需要:

  • 您有目标机器的本地管理员特权
  • RemoteRegistry 服务在对方机器上运行
  • 对方机器的本地防火墙启用了“远程管理例外”

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

当您启用了 ScriptBlockLogging 后,PowerShell 将会记录所有在您机器上执行的所有 PowerShell 代码。如果没有启用它,所有安全相关的代码仍然会记录。这样很不错。然而,该任何用户都可以读取该日志,所以任何人都可以类似这样浏览记录下的代码:

1
Get-WinEvent -FilterHashtable @{ ProviderName="Microsoft-Windows-PowerShell";  Id = 4104 }

To harden security and limit the access to the log file, you have two choices:
要强化安全并限制日志文件的读取,您有两个选择:

  • 您可以通过安装数字证书来设置加密的日志。通过这种方法,记录的数据可以被保护起来,甚至其他的管理员同事也无法读取。然而,设置和管理这些证书并不那么简单。
  • 您可以增强 PowerShell 操作日志的存取安全,并且使用和传统的安全日志相同的存取方式。通过这种方法,只有管理员可以读取该日志。这是我们今天要在本技能中讨论的方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#requires -RunAsAdministrator

# this is where the PowerShell operational log stores its settings
$Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\winevt\Channels\Microsoft-Windows-PowerShell/Operational"

# get the default SDDL security definition for the classic security log
$sddlSecurity = ((wevtutil gl security) -like 'channelAccess*').Split(' ')[-1]

# get the current SDDL security for the PowerShell log
$sddlPowerShell = (Get-ItemProperty -Path $Path).ChannelAccess

# store the current SDDL security (just in case you want to restore it later)
$existsBackup = Test-Path -Path $Path
if (!$existsBackup)
{
Set-ItemProperty -Path $Path -Name ChannelAccessBackup -Value $sddlPowerShell
}

# set the hardened security to the PowerShell operational log
Set-ItemProperty -Path $Path -Name ChannelAccess -Value $sddlSecurity

# restart the service to take effect
Restart-Service -Name EventLog -Force

当您运行该脚本时,读取 PowerShell 操作日志的权限被限制为只有本地管理员。

PowerShell 技能连载 - 使用注册表用户配置单元

读写注册表的 HKEY_LOCAL_USER 十分容易,因为这个配置单元对于所有用户都一致。那么如何读写其他用户的 HKEY_CURRENT_USER 配置单元?

假设您是管理员并且希望为其他用户的 HKEY_CURRENT_USER 配置单元添加注册表值。

首先您需要挂载该用户的用户配置单元。该配置单元位于该用户的用户配置文件下的 NTUSER.DAT 文件中。作为一个管理员,您需要先运行以下 PowerShell 代码来挂载 UserTobias 用户的用户配置文件:

1
PS C:\> REG LOAD HKEY_Users\UserTobias "C:\Users\Tobias\NTUSER.DAT"

该用户配置单元将挂载在 HKEY_USERS 下名为 UserTobias 的注册表键中,而且 PowerShell 可以类似这样存取该路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS C:\> Get-ChildItem -Path Registry::HKEY_USERS\UserTobias


Hive: HKEY_USERS\UserTobias


Name Property
---- --------
AppEvents
Console ColorTable00 : 789516
ColorTable01 : 14300928
ColorTable02 : 958739
ColorTable03 : 14521914
ColorTable04 : 2035653
ColorTable05 : 9967496
ColorTable06 : 40129

现在要读取甚至写入该指定用户的配置单元十分容易。以下代码将创建一个新的注册表键:

1
PS C:\> $null = New-Item -Path Registry::HKEY_USERS\UserTobias\Software\Microsoft\Windows\CurrentVersion\Test

以下是如何读取/写入一个值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PS C:\> Get-ItemProperty -Path Registry::HKEY_USERS\UserTobias\Software\Microsoft\OneDrive


EnableDownlevelInstallOnBluePlus : 0
EnableTHDFFeatures : 1
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_USERS\UserTobias\Software\Microsoft\OneDrive
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_USERS\UserTobias\Software\Microsoft
PSChildName : OneDrive
PSProvider : Microsoft.PowerShell.Core\Registry




PS C:\> Set-ItemProperty -Path Registry::HKEY_USERS\UserTobias\Software\Microsoft\OneDrive -Name EnableDownlevelInstallOnBluePlus -Value 1 -Type DWord

PS C:\> Get-ItemProperty -Path Registry::HKEY_USERS\UserTobias\Software\Microsoft\OneDrive


EnableDownlevelInstallOnBluePlus : 1
EnableTHDFFeatures : 1
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_USERS\UserTobias\Software\Microsoft\OneDrive
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_USERS\UserTobias\Software\Microsoft
PSChildName : OneDrive
PSProvider : Microsoft.PowerShell.Core\Registry

当您操作完 HKEY_USERS 注册表配置单元之后,别忘了卸载它:

1
PS C:\> $null = REG UNLOAD HKEY_Users\UserTobias

请注意这条命令将会抛出一个 “Access Denied” 错误,如果您没有管理员特权,或者该注册表配置单元正在被其他人使用。例如,如果您启动了 regedit.exe,当该用户配置单元加载以后,regedit.exe 可以显示加载的用户配置单元,而当 regedit 处于打开状态时,该配置单元被锁定并且无法关闭。

PowerShell 技能连载 - 管理 Windows 功能(第 2 部分)

在 Windows 10 中,不像 Windows Server,您无法使用 Get-WindowsFeatureAdd-WindowsFeature cmdlet 来管理 Windows 功能。

然而,对于客户端来说,有一个很类似的 cmdlet 可用:Enable-WindowsOptionalFeature。以下代码将添加 PowerShell Hyper-V cmdlet 和 Hyper-V 功能:

1
2
Enable-WindowsOptionalFeature -Online -All -FeatureName Microsoft-Hyper-V-Management-PowerShell -NoRestart
Enable-WindowsOptionalFeature -Online -All -FeatureName Microsoft-Hyper-V -NoRestart

执行的结果是一个对象,告知您是否需要重启。

请注意 -All 参数:如果您忽略了该参数,那么您需要自行确保所有先决条件和依赖项都已安装好,才能添加另一个新项。如果您懒,或者不了解依赖项,那么 -All 参数将自动为您安装所有必须的依赖项。

PowerShell 技能连载 - 管理 Windows 功能(第 1 部分)

Windows 10 带来一系列功能,但默认只安装了一个子集。您可以手工打开控制面板查看 Windows 功能。有经验的管理员也会使用 dism.exe 命令行工具。

在 PowerShell 中,您可以通过 Get-WindowsOptionalFeature 查看 Windows 功能的状态。当您指定了 -Online 参数,该 cmdlet 将返回当前可用的功能和它们的状态。

使用 Where-Object 命令,您可以容易地过滤结果,并且例如只显示未安装的功能清单:

1
2
3
4
5
6
7
# list all Windows features and their state
Get-WindowsOptionalFeature -Online | Out-GridView

# list only available features that are not yet installed
Get-WindowsOptionalFeature -Online |
Where-Object State -eq Disabled |
Out-GridView

PowerShell 技能连载 - 在 PowerShell 中运行 CMD 命令

PowerShell 默认情况下不支持原生的 cmd.exe 命令,例如 “dir“。替代的是,它使用历史别名 “dir“ 指向最接近的 PowerShell cmdlet:

1
2
3
4
5
PS C:\> Get-Command -Name dir | ft -AutoSize

CommandType Name Version Source
----------- ---- ------- ------
Alias dir -> Get-ChildItem

这解释了为什么 PowerShell 中的 “dir“ 不支持 cmd.exe 以及批处理文件中的开关和参数,例如 “cmd.exe/w“。

如果您必须使用 cmd 形式的命令,请用 /c 参数(代表 “command”)启动一个原生的 cmd.exe,执行这条命令,并在 PowerShell 内处理执行结果。这个例子运行 cmd.exe /c,然后以参数 /w 运行旧的 “dir“ 命令:

1
PS C:\> cmd.exe /c dir /w

一个更安全的方法是使用 “--%“ 操作符:

1
PS C:\> cmd.exe --% /c dir /w

当在参数之前添加了它之后,PowerShell 解释器将不会对参数进行处理,这样您甚至可以像在 cmd.exe 中一样使用环境变量注解。副作用是:您在参数中不再能使用 PowerShell 技术,比如变量:

1
PS C:\> cmd.exe --% /c dir %WINDIR% /w