PowerShell 技能连载 - 当发生错误时播放一段声音

为了吸引用户的注意力,您的脚本可以很容易地播放 WAV 声音文件。以下是一个简单的函数:

function Play-Alarm {
    $path = "$PSScriptRoot\Alarm06.wav"
    $playerStart = New-Object Media.SoundPlayer $path
    $playerStart.Load()
    $playerStart.PlaySync()
}

这段脚本假设 WAV 文件存放在和脚本相同的目录。请注意 PowerShell 2.0 并不支持 $PSScriptRoot

您只需要确保设置了 $path 变量并指向一个您希望的合法的 WAV 文件即可。

缺省情况下,PowerShell 将会等待直到声音播放完。如果您希望 PowerShell 继续执行而不是等待,请将 PlaySync() 替换成 Play()

PowerShell 技能连载 - 根据主机名获取 DNS IP 地址

有一个 GetHostByName() .NET 函数十分有用。它可以查询一个主机名并返回其当前的 IP 地址:

[System.Net.DNS]::GetHostByName('someName')

通过一个简单的 PowerShell 包装,它可以转换成一个多功能的很棒的小函数:

function Get-IPAddress
{
  param
  (
    [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [String[]]
    $Name
  )

  process
  { $Name | ForEach-Object { try { [System.Net.DNS]::GetHostByName($_) } catch { } }}
}

您现在可以直接使用这个函数(来获取您的 IP 地址)了。您可以传入一个或多个计算机名(逗号分隔)。您甚至可以通过 Get-ADComputer 或者 Get-QADComputer 管道传入数据。

Get-IPAddress
Get-IPAddress -Name TobiasAir1
Get-IPAddress -Name TobiasAir1, Server12, Storage1
'TobiasAir1', 'Server12', 'Storage1' | Get-IPAddress
Get-QADComputer | Get-IPAddress
Get-ADComputer -Filter * | Get-IPAddress

这样做是可行的,因为这个函数包含管道绑定以及一个参数序列化器。

-Name 参数为 ForEach-Object 提供数据,所以无论用户传入多少个机器名,它们都能被正确处理。

-Name 参数既能以参数的方式,也能以值的方式从管道中接收数据。所以您可以传入任何包含“Name”属性的对象,也可以传入任何纯字符串的列表。

注意该函数有一个非常简易的错误处理器。如果您传入了一个无法解析的计算机名,那么什么事也不会发生。如果您需要处理错误信息,请在 catch 代码块中添加代码。

PowerShell 技能连载 - 读写 NTFS 流

当一个文件存储在 NTFS 文件系统分区时,您可以向它附加数据流来存储隐藏信息。

以下是一个将 PowerShell 代码隐藏在 NTFS 流中的例子。当您运行这段代码时,它将在您的桌面上创建一个新的 PowerShell 脚本文件,然后在 ISE 编辑器中打开这个文件:

$path = "$home\Desktop\secret.ps1"

$secretCode = {
  Write-Host -ForegroundColor Red 'This is a miracle!';
  [System.Console]::Beep(4000,1000)
}

Set-Content -Path $path -Value '(Invoke-Expression ''[ScriptBlock]::Create((Get-Content ($MyInvocation.MyCommand.Definition) -Stream SecretStream))'').Invoke()'
Set-Content -Path $path -Stream SecretStream -Value $secretCode
ise $path

这个新的文件将看上去只是包含以下代码:

(Invoke-Expression '[ScriptBlock]::Create((Get-Content ($MyInvocation.MyCommand.Definition) -Stream SecretStream))').Invoke()

而当您运行这个脚本文件时,它将显示一段红色的文本并且蜂鸣一秒钟。所以新创建的脚本实际上执行了嵌入在隐藏 NTFS 流中名为“SecretStream”的代码。

要向 NTFS 卷中的(任何)文件附加隐藏信息,请使用 Add-ContentSet-Content 命令以及 -Stream 参数。

要从一个流中读取隐藏信息,请使用 Get-Content 命令,并为 -Stream 参数指定存储数据时用的名字。

PowerShell 技能连载 - 快速创建新的本地管理员账户

是否有为了测试而创建新的本地管理员账户的经历?假设您已经以 Administrator 账户登录,并且使用管理员特权开启 PowerShell,那么增加这样一个账户只需要几行代码就可以完成:

$user = 'splitpersonality'

net user /add $user
net localgroup Administrators /add $user

注意目标的组名是本地化的,所以在非英文系统中,您需要将 Administrators 替换成您的 Administrators 组的本地化名称。

译者注:我在中文操作系统上实验了一下,直接用 Administrators 也没有问题的。

PowerShell 技能连载 - 启动任何版本的 Excel

Microsoft Excel 是一个不那么容易直接运行的程序的例子:Excel 的路径也许是各不相同的,取决于 Office 的版本以及平台的架构(32 位或 64 位)。

PowerShell 有一个十分智能的 cmdlet 来用于运行程序:Start-Process。通常您可以以这种方式用它来运行 Excel(或其它可执行程序):

PS> Start-Process -FilePath 'C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.EXE'

而在您的系统中, Excel 的路径可能不同。这是为什么 Start-Process 设计成接受通配符的原因。只要将路径中所有“特定的”部分替换为一个通配符即可。

以下代码将会运行任何版本的 Excel,而不论是什么平台架构:

PS> Start-Process -FilePath 'C:\Program*\Microsoft Office\Office*\EXCEL.EXE'

PowerShell 技能连载 - 解锁下载的文件

任何从 Internet 下载的以及从邮件接收到的文件,都被 Windows 隐式地标记为不安全的。如果文件包含可执行文件或二进制文件,它们必须解锁以后才可以运行。

PowerShell 3.0 以及以上的版本可以检测到包含“下载标记”的文件:

Get-ChildItem -Path $Home\Downloads -Recurse |
  Get-Item -Stream Zone.Identifier -ErrorAction Ignore |
  Select-Object -ExpandProperty FileName |
  Get-Item

这段代码或许不会返回任何文件(当没有文件具有下载标记的情况下),或许会返回一大堆文件(这也许意味着您解压了一个下载的 ZIP 文件,但忘了先对它解锁)。

要解锁这些文件,请使用 Unblock-File cmdlet。这段代码将解锁您下载文件夹中当前被锁定的文件(不涉及到其它任何文件):

Get-ChildItem -Path $Home\Downloads -Recurse |
  Get-Item -Stream Zone.Identifier -ErrorAction Ignore |
  Select-Object -ExpandProperty FileName |
  Get-Item |
  Unblock-File

Microsoft Visual Studio 2013 Update 1 离线安装包下载

下载信息

Visual Studio 2013 Update1 官网(微软下载中心)

本次更新是适用于 Visual Studio 2013 的一系列新增功能和 Bug 修复中的最新更新。

  • 出版日期:2014/1/17
  • 版本:30110.00

在线安装包(1.1 MB)

  • CRC :AD470B9E
  • SHA-1:D3543BFE1F0DA6D7D63760C5C3A5A7166E2B6B42

离线安装包(光盘镜像)(245 MB)

  • CRC :B9C46697
  • SHA-1:51403CAF8E5E9799ACF1F3A0DA0E46390CD2FB16

PowerShell 技能连载 - 删除空结果

要排除某些包含空属性值的结果,您可以简单地使用 Where-Object 命令。例如,当您运行 Get-HotFix 时,假设您只希望查看 InstalledOn 属性包含时间值的补丁,以下是解决方案:

PS> Get-HotFix | Where-Object InstalledOn

类似地,要从 WMI 中获取分配了 IP 地址的网络适配器,请使用以下代码:

PS> Get-WmiObject -Class Win32_NetworkAdapterConfiguration | Where-Object IPAddress

请注意在 PowerShell 2.0 以及以下版本,您需要使用完整的语法,类似如下:

PS> Get-WmiObject -Class Win32_NetworkAdapterConfiguration | Where-Object { $_.IPAddress }

Where-Object 将会排除您所选的属性包含以下任意一种情况的对象:null 值、空字符串,或者数字 0。因为这些值在转换为 Boolean 类型的时候将会被转换成 $false

PowerShell 技能连载 - PowerShell 4.0 中隐藏的数组扩展方法

PowerShell 4.0 (Windows 8.1 自带)中的数组原生支持 Foreach 和 Where 操作。这是一个 geek 的写法,所以并不见得比传统的管道有明显的优势(除了也许性能有所提升之外)。

这行代码将从一个数字列表中过滤出奇数来:

@(1..10).Where({$_ % 2})

以下代码将获取正在运行中的服务:

@(Get-Service).Where({$_.Status -eq 'Running'})

还有一些更多的(不在文档中)的东西。这行代码将获取大于 2 的前 4 个数字:

@(1..10).Where({$_ -gt 2}, 'skipuntil', 4)

最后,以下代码将做类似的事情,但是将它们转换为 TimeSpan 对象:

@(1..10).Where({$_ -gt 2}, 'skipuntil', 5).Foreach([Timespan])

用 PowerShell 移除 Evernote 的广告

破解过程

Evernote(印象笔记)免费用户的左下角有个正方形的广告,点击关闭按钮反而会出来一个对话框:

虽然破解 + 写脚本 + 写这篇博客花了一两个小时,但是如果能节约更多读者的时间,并且提高一点技术水平,也算有益了吧。

我们用 Visual Studio 中的 Spy++ 查看一下控件的窗口类名:

得到的结果是“ENAdBrowserCtrl”,从窗口类名来看,似乎是为了广告而设计的。出于保险起见,用 WinHex 搜索了一下,这个字符串只出现一次,并且是采用双字节编码的“45004E0041006400420072006F0077007300650072004300740072006C”。

我们尝试破坏这个字符串试试:用 WinHex 的“填充选块”功能,将这块区域替换成 00,然后保存运行,广告果然没有了。

但是手工修改毕竟比较麻烦,而且未来版本更新以后还要再次破解。所以简单写了个 PowerShell 脚本来自动完成破解。

PowerShell 自动化脚本

请将以下代码保存成 Remove-EvernoteAD.ps1 并以管理员身份执行 :)
脚本的思路是以二进制的方式搜索指定的模式(pattern),并替换成新的模式。涉及到一些字节操作和进制转换。

$pattern = '45004E0041006400420072006F0077007300650072004300740072006C'
$replacement = $pattern -replace '.', '0'

function Replace-Pattern ($buffer, $pattern, $replacement) {
    $isPatternMatched = $false
    for ($offset = 6220000; $offset -lt $buffer.Length - $pattern.Length; $offset++) {
        $isByteMatched = $true
        for ($patternOffset = 0; $patternOffset -lt $pattern.Length; $patternOffset++) {
            if ($buffer[$offset + $patternOffset] -ne $pattern[$patternOffset]) {
                $isByteMatched = $false
                break
            }
        }
        if ($isByteMatched) {
            $isPatternMatched = $true
            break
        }
    }

    if ($isPatternMatched) {
        for ($index = 0; $index -lt $pattern.Length; $index++) {
            $buffer[$offset + $index] = [byte]0
        }

        return $true
    } else {
        return $false
    }
}

function Convert-HexStringToByteArray
{
    ################################################################
    #.Synopsis
    # Convert a string of hex data into a System.Byte[] array. An
    # array is always returned, even if it contains only one byte.
    #.Parameter String
    # A string containing hex data in any of a variety of formats,
    # including strings like the following, with or without extra
    # tabs, spaces, quotes or other non-hex characters:
    # 0x41,0x42,0x43,0x44
    # x41x42x43x44
    # 41-42-43-44
    # 41424344
    # The string can be piped into the function too.
    ################################################################
    [CmdletBinding()]
    Param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [String] $String )

    #Clean out whitespaces and any other non-hex crud.
    $String = $String.ToLower() -replace '[^a-f0-9\\\,x\-\:]',''

    #Try to put into canonical colon-delimited format.
    $String = $String -replace '0x|\\x|\-|,',':'

    #Remove beginning and ending colons, and other detritus.
    $String = $String -replace '^:+|:+$|x|\\',''

    #Maybe there's nothing left over to convert...
    if ($String.Length -eq 0) { ,@() ; return }

    #Split string with or without colon delimiters.
    if ($String.Length -eq 1)
    { ,@([System.Convert]::ToByte($String,16)) }
    elseif (($String.Length % 2 -eq 0) -and ($String.IndexOf(":") -eq -1))
    { ,@($String -split '([a-f0-9]{2})' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}}) }
    elseif ($String.IndexOf(":") -ne -1)
    { ,@($String -split ':+' | foreach-object {[System.Convert]::ToByte($_,16)}) }
    else
    { ,@() }
    #The strange ",@(...)" syntax is needed to force the output into an
    #array even if there is only one element in the output (or none).
}

echo '本程序用于去除 Evernote 非会员左下角的正方形广告。'
echo '请稍候……'

$patternArray = Convert-HexStringToByteArray $pattern
$replacementArray = Convert-HexStringToByteArray $replacement


$path = "${Env:ProgramFiles}\Evernote\Evernote\Evernote.exe"
$path86 = "${Env:ProgramFiles(x86)}\Evernote\Evernote\Evernote.exe"
if (Test-Path $path) {
    $execute = Get-Item $path
} elseif (Test-Path $path86) {
    $execute = Get-Item $path86
} else {
    Write-Warning '没有找到 Evernote.exe。'
    exit
}

$exe = gc $execute -ReadCount 0 -Encoding byte

if (Replace-Pattern $exe $patternArray $replacementArray) {
    $newFileName = $execute.Name + '.bak'
    $newPath = Join-Path $execute.DirectoryName $newFileName
    Stop-Process -Name Evernote -ErrorAction SilentlyContinue
    Move-Item $execute $newPath
    Set-Content $execute -Value $exe -Encoding Byte
    echo '广告去除成功!Evernote 未来升级后需重新运行本程序。'
    Start-Process $execute
} else {
    Write-Warning '无法去除广告,是否已经去除过了?'
    if (!(Get-Process -Name Evernote -ErrorAction SilentlyContinue)) {
        Start-Process $execute
    }
}

您也可以从这里 下载 写好的脚本。