PowerShell 技能连载 - 凭据混淆器

适用于 PowerShell ISE 3.0 及以上版本

虽然一般不建议将密码硬编码在脚本里,但有些情况下已经这么做了。相比于硬编码明文密码,一个最基本的改进是将密码混淆。密码混淆是一种弱的保护方式,但它能确保非掌握 PowerShell 知识的人员轻易地得到密码。

这是段小脚本会询问用户名和密码,然后生成一段混淆脚本来产生凭据对象。

当您运行下面这段脚本生成的脚本,在 $cred 变量将会保存一个包含用户名和密码的凭据对象,它可以用于任何带 -Credential 参数的 cmdlet。

$cred = Get-Credential -Message 'Enter Domain\Username and Password'
$pwd = $cred.Password
$user = $cred.UserName
$key = 1..32 | ForEach-Object { Get-Random -Maximum 256 }
$pwdencrypted = $pwd | ConvertFrom-SecureString -Key $key

$private:ofs = ' '

$generatedScript = @()
$generatedScript += '$password = ''{0}''' -f $pwdencrypted
$generatedScript += '$key = ''{0}''' -f "$key"

$generatedScript += '$passwordSecure = ConvertTo-SecureString -String $password -Key ([Byte[]]$key.Split('' ''))'
$generatedScript += '$cred = New-Object system.Management.Automation.PSCredential(''{0}'', $passwordSecure)' -f $user
$generatedScript += '$cred'

$file = $psise.CurrentPowerShellTab.Files.Add()
$file.Editor.Text = $generatedScript | Out-String
$file.Editor.SetCaretPosition(1,1)

自动生成的密码脚本看起来类似这样:

$password = '76492d1116743f0423413b16050a5345MgB8AHMAUQA3AFAAVwB0AGkAUQBUAC8AdwBqADYAUABVAFYAUwB4AEYAYgB4AFEAPQA9AHwAZgA0ADgAOQA4AGYANwA0AGEAMAA0ADUANwA5ADkAMwA5ADkAMwA1ADUANQA0AGYANwA5AGQANwBkAGYAOQBmAGEAYQA3ADMAYgBkADIAOQA3AGMAYQBmADUAMgA3ADEANwA3AGEAYgBmADAAYgA1AGYAYwAyADYAYgAzADkAOAA='
$key = '187 98 34 82 148 52 13 86 246 2 130 197 217 97 147 98 75 197 149 246 74 35 27 7 211 15 131 93 182 231 171 3'
$passwordSecure = ConvertTo-SecureString -String $password -Key ([Byte[]]$key.Split(' '))
$cred = New-Object system.Management.Automation.PSCredential('mickey\mouse', $passwordSecure)
$cred

PowerShell 技能连载 - 自动展开和内存消耗

适用于 PowerShell 3.0 及以上版本

在 PowerShell 3.0 中,增加了一个称为“自动回滚”的特性。通过这个特性,您可以这样书写代码:

(Get-ChildItem -Path $env:windir\system32 -Filter *.dll).VersionInfo

这行代码查找 System32 子文件夹下的所有 DLL 文件并且对它们进行迭代,对每个文件返回其 VersionInfo 属性(实际上是 DLL 版本)。在使用自动展开功能之前,您需要手工编写循环语句:

Get-ChildItem -Path $env:windir\system32 -Filter *.dll | ForEach-Object { $_.VersionInfo }

当您运行以上两段代码时,它们返回完全相同的结果。然而,您将立刻发现自动展开特性所带来的代价:它消耗了更多的时间才返回结果。第一行结果出来时可能要消耗 10 秒之多的时间,而“传统”的方法几乎是连续地返回信息。

总体消耗的时间是差不多的。实际上,自动展开特性等价的代码如下:

$data = Get-ChildItem -Path $env:windir\system32 -Filter *.dll
Foreach ($element in $data) { $element.VersionInfo }

自动展开代码更直观,更容易书写手写循环兼容性更好,更快输出结果。

PowerShell 技能连载 - 复制命令行历史

适用于 PowerShell 所有版本

要将 PowerShell 会话中键入过的所有 PowerShell 命令保存下来,请试试这行代码:

(Get-History).CommandLine | clip.exe

它将所有的命令拷贝至剪贴板。然后您就可以将它们粘贴到 PowerShell ISE 并保存为文件。

PowerShell 技能连载 - 获取计算机序列号

适用于 PowerShell 所有版本

在前一个技巧里我们演示了如何通过 DELL 的序列号在线检查保修状态。其它厂家也会提供类似的服务。

这段代码可以读取序列号:

$ComputerName = $env:COMPUTERNAME

$serial = (Get-WmiObject -ComputerName $ComputerName -Class Win32_BIOS).SerialNumber
"Your computer serial is $serial"

PowerShell 技能连载 - 在线检测 DELL 保修

适用于 PowerShell 2.0 及以上版本

如果您拥有一台 DELL 电脑,您可以通过 Web Service 提交电脑的序列号得到授权信息:

$serial = '36GPL41'

$service = New-WebServiceProxy -Uri http://143.166.84.118/services/assetservice.asmx?WSDL
$guid = [Guid]::NewGuid()

$info = $service.GetAssetInformation($guid,'warrantycheck',$serial)
$info.Entitlements

结果可能看起来如下:

$info.Entitlements


ServiceLevelCode        : TS
ServiceLevelDescription : P, ProSupport
Provider                : DELL
StartDate               : 23.03.2004 00:00:00
EndDate                 : 23.03.2007 00:00:00
DaysLeft                : 0
EntitlementType         : Expired

ServiceLevelCode        : ND
ServiceLevelDescription : C, NBD ONSITE
Provider                : UNY
StartDate               : 23.03.2005 00:00:00
EndDate                 : 23.03.2007 00:00:00
DaysLeft                : 0
EntitlementType         : Expired

ServiceLevelCode        : ND
ServiceLevelDescription : C, NBD ONSITE
Provider                : UNY
StartDate               : 23.03.2004 00:00:00
EndDate                 : 24.03.2005 00:00:00
DaysLeft                : 0
EntitlementType         : Expired

这些从 Web Service 返回的信息还包括了其它有用的信息,例如计算机的系统类型:

PS> $info.AssetHeaderData


ServiceTag     : 36GPL41
SystemID       : PLX_PNT_CEL_GX270
Buid           : 11
Region         : Americas
SystemType     : OptiPlex
SystemModel    : GX270
SystemShipDate : 23.03.2004 07:00:00

PowerShell 技能连载 - 用 Cmdlet 来管理 MSI 安装包

适用于 PowerShell 2.0 及以上版本

需要管理 MSI 安装包的朋友可以从这个开源项目中受益:http://psmsi.codeplex.com/

只需要下载 PowerShell 模块——它自己包含了一个安装包。请确保在安装它之前对 MSI 文件进行解锁。否则,Windows 可能会拒绝安装它。

不幸的是,这个模块将它自己安装到一个很特殊的地方(AppData\Local\Apps...),并且扩展了 $env:PSModulePath 环境变量,所以 PowerShell 可以找到这个模块。这是为什么您在安装完模块之后需要重启 PowerShell 的原因,因为 PowerShell 不能自动感知到 $env:PSModulePath 发生了改变。

这是获取新的 MSI 相关 cmdlet 的方法:

PS> Get-Command -Module MSI

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Function        Get-MSIComponentState                              MSI
Function        Get-MSISharedComponentInfo                         MSI
Function        Install-MSIAdvertisedFeature                       MSI
Cmdlet          Add-MSISource                                      MSI
Cmdlet          Clear-MSISource                                    MSI
Cmdlet          Edit-MSIPackage                                    MSI
Cmdlet          Export-MSIPatchXml                                 MSI
Cmdlet          Get-MSIComponentInfo                               MSI
Cmdlet          Get-MSIFeatureInfo                                 MSI
Cmdlet          Get-MSIFileHash                                    MSI
Cmdlet          Get-MSIFileType                                    MSI
Cmdlet          Get-MSILoggingPolicy                               MSI
Cmdlet          Get-MSIPatchInfo                                   MSI
Cmdlet          Get-MSIPatchSequence                               MSI
Cmdlet          Get-MSIProductInfo                                 MSI
Cmdlet          Get-MSIProperty                                    MSI
Cmdlet          Get-MSIRelatedProductInfo                          MSI
Cmdlet          Get-MSISource                                      MSI
Cmdlet          Get-MSISummaryInfo                                 MSI
Cmdlet          Get-MSITable                                       MSI
Cmdlet          Install-MSIPatch                                   MSI
Cmdlet          Install-MSIProduct                                 MSI
Cmdlet          Measure-MSIProduct                                 MSI
Cmdlet          Remove-MSILoggingPolicy                            MSI
Cmdlet          Remove-MSISource                                   MSI
Cmdlet          Repair-MSIProduct                                  MSI
Cmdlet          Set-MSILoggingPolicy                               MSI
Cmdlet          Test-MSIProduct                                    MSI
Cmdlet          Uninstall-MSIPatch                                 MSI
Cmdlet          Uninstall-MSIProduct                               MSI

PowerShell 技能连载 - 读取多行文本

适用于 PowerShell 3.0 及以上版本

有些时候您偶然会见到类似这样的技巧:

$FilePath = "$env:SystemRoot\WindowsUpdate.log"

$ContentsWithLinebreaks = (Get-Content $FilePath) -join "`r`n"

您能否出猜出它的用意?Get-Content 缺省情况下返回由一行一行组成的字符串数组,然后 -join 操作符将该数组转化为一个字符串。

从 PowerShell 3.0 开始,Get-Content 多了一个参数:-Raw。它比起刚才的方法高效的多,并且可以得到相同的结果:

$FilePath = "$env:SystemRoot\WindowsUpdate.log"

$ContentsWithLinebreaks = (Get-Content $FilePath) -join "`r`n"

$ContentsWithLinebreaks2 = Get-Content $FilePath -Raw

$ContentsWithLinebreaks -eq $ContentsWithLinebreaks2

当您使用这段代码时,会发现 $ontentWithLinebreaks$ContentWithLinebreaks2 是不同的。唯一的区别是在 $ContentsWithLinebreaks2 尾部有一个换行符:

PS> $ContentsWithLinebreaks -eq $ContentsWithLinebreaks2.TrimEnd("`r`n")
True

PS>

PowerShell 技能连载 - 要求管理员权限

适用于 PowerShell 4.0 及以上版本

如果您知道某个脚本需要管理员权限,只需要一个简单的 #requres 语句就可以确保符合该需求的才可以运行:

#requires -version 4.0
#requires –runasadministrator


'I am Admin!'

如果这个脚本没有使用管理员身份运行,它将显示一个有意义错误提示信息,说明它为何无法运行。

实际上,在这个例子中您可以看到两条 #requires 语句。第一条确保该脚本至少运行在 PowerShell 4.0 以上的环境中,这是第二条 #requires 的先决条件。它是由 PowerShell 4.0 引入的,不支持 PowerShell 更低的版本。

所以最好不要在 PowerShell 3.0 或更早的环境中使用这个技术。在那些环境中,您还是需要手工确认脚本是否拥有管理员权限。

JavaScript 中的匿名函数和立即执行函数

命名函数

1
2
3
function foo() {

}

这种写法和 C 语言中定义一个函数的写法差不多。

匿名函数 + 赋值语句

1
2
3
var bar = function() {

};

例子中变量 bar 指向一个匿名函数。由于这是一个赋值语句,所以应该以 ; 结尾。虽然分号可以省略。

命名函数 + 赋值语句

1
2
3
var bar = function foo() {

};

此处的 foo 可以省略,不会影响代码逻辑。但是加上 foo 在调试工具中查看调用堆栈时,可以更清晰地看到函数的名称。所以这是一个推荐的实践。

立即执行函数 (IIFE)

1
2
3
(function() {

}());

还有一种变体:

1
2
3
(function() {

})();

请注意上述两种写法的圆括号的位置区别。前一种写法是 JSLint 推荐的写法,所以推荐采用第一种写法。

这种模式本质上只是一个函数表达式(无论是命名或匿名的),该函数会在创建后立刻执行。以下是用命名函数来实现 IIFE 的例子:

1
2
3
(function foo() {
var bar = 1;
}());

代码中用 foo 作为 IIFE 的名字。

这种模式是非常有用的,因为它为初始化代码提供了一个作用于沙箱(sandbox)。代码中的 bar 变量此时会成为一个局部变量,不会污染全局的 window (global) 对象。