用 PowerShell 重新打包 0day appz

从 0day 服务器下载下来的 appz 文件夹是这样的形态:

每个文件夹代表一个 appz 软件,打开是这个样子的:

里面是一系列 .zip 文件以及说明文件。这些 .zip 文件却不是使用 zip 的分卷压缩出来的,它们的内容如下:

要把这些 .zip 文件全部解压到同一个目录下,才可以得到一系列 rar 的分卷压缩文件。我们打开一个 .rar 文件,这才看到真正的内容:

软件数量大的时候,人工重复进行上述操作就不合适了。机械的劳动应该交给程序。我们可以设计一个 PowerShell 脚本,完成一系列功能:

  • 遍历 0day appz 的下载目录。
  • 解压所有 .zip 文件。
  • 解压 .rar 文件。
  • 将说明文件复制到一起。
  • 将最终的文件重打包为 .zip 文件。
  • 如果上述的解压有问题,则不打包,并输出错误日志。
  • 清理临时文件。
  • 清理成功的原始文件夹,保留失败的原始文件夹。

按照这个需求,我们可以编写如下 PowerShell 脚本:

$DebugPreference = 'Continue'

$incoming = 'd:\0day\incoming'
$temp1 = 'd:\0day\temp1'
$temp2 = 'd:\0day\temp2'
$output = 'd:\0day\output'

if (Test-Path $temp1) { del $temp1 -r }
if (Test-Path $temp2) { del $temp2 -r }

$apps = dir $incoming -Directory
$count = 0
$hasFailed = $false
$apps | foreach {
    $name = $_.Name
    Write-Progress -Activity 'Repacking apps' -PercentComplete ($count / $apps.Length * 100) -CurrentOperation $name
    echo "Repacking $name"

    md $temp1 | Out-Null
    md $temp2 | Out-Null

    # d:\0day\util\7z x -o"d:\0day\temp1" "d:\0day\incoming\VanDyke.SecureCRT.v7.2.2.491.Incl.Patch.And.Keymaker-ZWT\*.zip"
    $arguments = 'x', "-o""$temp1""", '-y', (Join-Path $_.FullName *.zip)
    .\7z $arguments | Out-Null

    if (!$?) {
        Write-Warning "Repacking $name failed."
        echo "$name" >> "$output\fail.log"

        del $temp1 -r
        del $temp2 -r

        $count++
        $hasFailed = $true
        return
    }

    # d:\0day\util\7z x -o"d:\0day\temp2" "d:\0day\temp1\*.rar" -y
    $arguments = 'x', "-o""$temp2""", '-y', "$temp1\*.rar"
    .\7z $arguments | Out-Null
    if (!$?) {
        Write-Warning "Repacking $name failed."
        echo "$name" >> "$output\fail.log"

        del $temp1 -r
        del $temp2 -r

        $count++
        $hasFailed = $true
        return
    }

    # copy d:\0day\temp1\*.diz d:\0day\temp2
    # copy d:\0day\temp1\*.nfo d:\0day\temp2

    dir $temp1 | where {
        $_.Extension -notmatch 'rar|r\d*'
    } | copy -Destination $temp2

    #d:\0day\util\7z a "d:\0day\output\VanDyke.SecureCRT.v7.2.2.491.Incl.Patch.And.Keymaker-ZWT.zip" "d:\0day\temp2\*.*" -r
    $arguments = 'a', "$output\$name.zip", "$temp2\*.*", '-r'
    .\7z $arguments | Out-Null
    if (!$?) {
        Write-Warning "Repacking $name failed."
        echo "$name" >> "$output\fail.log"

        del $temp1 -r
        del $temp2 -r

        $count++
        $hasFailed = $true
        return
    }

    del $temp1 -r
    del $temp2 -r

    Remove-Item -LiteralPath $_.FullName -r

    $count++
}

if ($hasFailed) {
    echo '' >> "$output\fail.log"
}

echo 'Press any key to continue...'
[Console]::ReadKey() | Out-Null

# del 'd:\0day\output\*.*' -r

您也可以在这里下载写好的脚本,包括完整的目录结构和 7z 软件包。请解压到 d:\ 中使用,或者自行调整脚本头部的路径。

PowerShell 技能连载 - 批量重命名对象的属性

有些时候,我们需要批量重命名对象的属性来更好地创建报表。例如,假设您获取了进程对象,你需要您可能需要以新的的列名来创建报表。

以下是一个称为 Rename-Property 的过滤器,可以用于重命名任何属性。在例子中,生成了一个进程列表,然后重命名一些属性:

filter Rename-Property ([Hashtable]$PropertyMapping)
{
    Foreach ($key in $PropertyMapping.Keys)
    {
        $_ = $_ | Add-Member -MemberType AliasProperty -Name $PropertyMapping.$key -Value $key  -PassThru
    }
    $_
}

$newProps = @{
  Company = 'Manufacturer'
  Description = 'Purpose'
  MainWindowTitle = 'TitlebarText'
}

# get raw data
Get-Process |
    # add alias properties as specified in $newProps
    Rename-Property $newProps |
    # select the properties you want to display
    # can be original properties and/or newly added alias properties
    Select-Object -Property Name, Manufacturer, Purpose, TitlebarText

Rename-Property 自动加入了 $newProps 中指定的所有属性。结果对象中含有名为“Manufacturer”、“Purpose”和“TitlebarText”的新属性。您可以接着使用 Select-Object 来选择您想在报表中包含的属性。您可以从原先存在的属性中选择,也可以从新增加的别名属性中选择。

所以本质上上,属性并没有被改名(技术上不可能实现)。实际上,该过滤器以新的名字添加了别名属性,并指向原先的属性。

PowerShell 技能连载 - 修正 Excel 报表中的显示

当您发送信息到 Microsoft Excel 中时,它将被 .NET 内置的 ToString() 方法转化为文本。这个方法通常并不能正确地转化数组或非基本数据类型。

以下是一个例子演示这个问题。它创建了一个您系统事件日志中 10 个最近错误事件的报表:

$Path = "$env:temp\$(Get-Random).csv"

Get-EventLog -LogName System -EntryType Error -Newest 10 |
  Select-Object EventID, MachineName, Data, Message, Source, ReplacementStrings, InstanceId, TimeGenerated |
  Export-Csv -Path $Path -Encoding UTF8 -NoTypeInformation -UseCulture

Invoke-Item -Path $Path

“Data”字段和“ReplacementStrings”无法使用。由于两个属性都包含数组,自动转换的结果只是简单显示数据的类型的名称。这是在从对象数据中创建 Excel 报表的常见现象。

要改进报表,您可以显式地使用 PowerShell 引擎将对象转化为文本,然后将多行文本转换为单行文本。

您可以对每个看起来不正确的字段运用这个方法。以下是上一个例子的解决方案,它能够改进 Message、Data 和 ReplacementStrings 字段的显示:

$Path = "$env:temp\$(Get-Random).csv"

Get-EventLog -LogName System -EntryType Error -Newest 10 |
  Select-Object EventID, MachineName, Data, Message, Source, ReplacementStrings, InstanceId, TimeGenerated |
  ForEach-Object {
    $_.Message = ($_.Message | Out-String -Stream) -join ' '
    $_.Data = ($_.Data | Out-String -Stream) -join ', '
    $_.ReplacementStrings = ($_.ReplacementStrings | Out-String -Stream) -join ', '

    $_
  } |
  Export-Csv -Path $Path -Encoding UTF8 -NoTypeInformation -UseCulture

Invoke-Item -Path $Path

现在所有字段都显示了正确的结果。请注意有问题的属性首先通过管道发送到 Out-String 命令(用 PowerShell 内部的机制将数据转换为有意义的文本),然后用 -join 将信息连接成单行文本。

还请注意“Message”属性是如何处理的。虽然这个属性看起来没问题,但是它实际上有可能是多行文本。多行信息在 Excel 中将只显示第一行,并以“…”结尾。我们将这些行通过空格连接之后,Excel 便可以显示完整信息了。

PowerShell 技能连载 - 创建 Excel 报表

PowerShell 对象可以很容易地通过 Microsoft Excel 打开。只需要将对象导出成 CSV,然后通过关联的应用程序打开 CSV 文件(如果装了 Excel,那么将用 Excel 打开)。

以下代码创建一个当前运行的进程报告,并用 Excel 打开:

$Path = "$env:temp\$(Get-Random).csv"
$originalProperties = 'Name', 'Id', 'Company', 'Description', 'WindowTitle'

Get-Process |
  Select-Object -Property $originalProperties |
  Export-Csv -Path $Path -Encoding UTF8 -NoTypeInformation -UseCulture

Invoke-Item -Path $Path

请注意 -UseCulture 如何根据您的区域设置自动选择正确的分隔符。

PowerShell 技能连载 - PowerShell 不支持 JSON 数据类型

缺省情况下,从 JSON 创建的对象使用 String 作为数据的类型:

$json = @"
{
    "Name": "Weltner",
    "ID" : "123"
 }
"@

$info = ConvertFrom-Json -InputObject $json
$info.Name
$info.ID

但是,JSON 不支持数据类型,虽然 JSON 数据类型并不等价于 .NET 数据类型。请注意在以下代码中,“ID”被定义成“数字”型,并且它被赋予一个数值型的值,而不需要用双引号引起来。

$json = @"
{
    "Name": "Weltner",
    number: "ID" : 123
 }
"@

$info = ConvertFrom-Json -InputObject $json
$info.Name
$info.ID

然而,当使用 ConvertFrom-Json 时,我们发现 PowerShell 并没有关心数据类型定义。它总是将值转换为 String 数据。

PowerShell 技能连载 - 用 JSON 来创建对象

JSON 用来描述对象的,类似 XML,但是 JSON 更简单得多.JSON 支持嵌套的对象属性,所以您可以从各种数据源中获取信息,然后将它们合并成一个自定义对象。

让我们来看看效果。以下代码创建一个清单条目,包含了电脑的许多详细信息:

$json = @"
{
    "ServerName": "$env:ComputerName",
    "UserName": "$env:UserName",
    "BIOS": {
         "Manufacturer" : "$((Get-WmiObject -Class Win32_BIOS).Manufacturer)",
         "Version" : "$((Get-WmiObject -Class Win32_BIOS).Version)",
         "Serial" : "$((Get-WmiObject -Class Win32_BIOS).SerialNumber)"
         },
    "OS" : "$([Environment]::OSVersion.VersionString)"
 }
"@

$info = ConvertFrom-Json -InputObject $json

$info.ServerName
$info.BIOS.Version
$info.OS

您接下来可以操作结果对象——获取信息,或增加、更新详细信息。

如果您对对象做了改动,可以用 ConvertTo-Json 将它序列化为 JSON 对象格式:

PowerShell 技能连载 - 获取父作用域中的变量值

如果您在一个函数中定义了变量,那么这些变量只在函数作用域内有效。要查看在外层作用域的变量值,请使用带 -Scope 参数的 Get-Variable 命令:

$a = 1

function test
{
    $a = 2
    $parentVariable = Get-Variable -Name a -Scope 1
    $parentVariable.Value
}

test

当脚本调用“test”函数时,函数定义了一个 $a 并且将它的值设为 2。在调用者作用域中,变量 $a 的值是 1。通过 Get-Variable,函数内可以得到外层作用域中的变量值。

PowerShell 技能连载 - 更新 Windows Defender 病毒定义

Windows 8.1 带来了一系列新的 cmdlet。其中一个可以自动下载并安装 Windows Defender 最新的反病毒定义。

Get-MpComputerStatus 返回当前病毒定义的信息。

这些 cmdlet 不是 PowerShell 的一部分,而是 Windows 8.1 的一部分,所以在早期版本的操作系统中,您会碰到找不到命令的错误信息。

PowerShell 技能连载 - 键盘鼠标自动化

某些时候,唯一的自动化处理办法是向 UI 组件发送按键和鼠标点击消息。一个强大且免费的 PowerShell 扩展,叫做“WASP”,地址如下:

http://wasp.codeplex.com/

一旦您装好了这个模块(解压前别忘了解除 ZIP 文件锁定。方法是右键点击,属性,解锁),WASP 模块提供以下 cmdlet:

以下是一个简单的操作 Windows 计算器的例子:

Import-Module WASP

# launch Calculator
$process = Start-Process -FilePath calc -PassThru
$id = $process.Id
Start-Sleep -Seconds 2
$window = Select-Window | Where-Object { $_.ProcessID -eq $id }

# send keys
$window | Send-Keys 123
$window | Send-Keys '{+}'
$window | Send-Keys 999
$window | Send-Keys =

# send CTRL+c
$window | Send-Keys '^c'

# Result is now available from clipboard

以下是附加说明:

  • 启动一个进程之后,要等待 1-2 秒,待窗体创建号以后才可以用 WASP 找到该窗口。
  • 参考 SendKeys API 发送按键。有些字符需要通过两边加花括号的方式转义。更多细节请参见这里:http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.send(v=vs.110).aspx/
  • 当需要发送按键序列,例如 CTRL+C 时,请使用小写字母。“^c”代表发送 CTRL+c,而“^C”代表发送 CTRL+SHIFT+C
  • 只有 WinForm 窗口支持操作子控件,例如特定的文本框和按钮(Select-ChildWindow, Select-Control)。WPF 窗口也可以接收按键,但是 WPF 中在窗体的 UI 组件之上无法获得支持输入的控件。

PowerShell 技能连载 - 显示 WPF 消息提示

WPF (Windows Presentation Foundation) 是一种创建窗体和对话框的技术。WPF 的好处是窗体设计和程序代码可以分离。

以下是一个显示醒目消息的例子。消息内容定义在 XAML 代码中,看起来类似 HTML (不过是区分大小写的)。您可以很容易地调整字体大小、文字、颜色等。不需要改任何程序代码:

$xaml = @"
<Window
 xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>

 <Border BorderThickness="20" BorderBrush="Yellow" CornerRadius="9" Background='Red'>
  <StackPanel>
   <Label FontSize="50" FontFamily='Stencil' Background='Red' Foreground='White' BorderThickness='0'>
    System will be rebooted in 15 minutes!
   </Label>

   <Label HorizontalAlignment="Center" FontSize="15" FontFamily='Consolas' Background='Red' Foreground='White' BorderThickness='0'>
    Worried about losing data? Talk to your friendly help desk representative and freely share your concerns!
   </Label>
  </StackPanel>
 </Border>
</Window>
"@

$reader = [System.XML.XMLReader]::Create([System.IO.StringReader] $xaml)
$window = [System.Windows.Markup.XAMLReader]::Load($reader)
$Window.AllowsTransparency = $True
$window.SizeToContent = 'WidthAndHeight'
$window.ResizeMode = 'NoResize'
$Window.Opacity = .7
$window.Topmost = $true
$window.WindowStartupLocation = 'CenterScreen'
$window.WindowStyle = 'None'
# show message for 5 seconds:
$null = $window.Show()
Start-Sleep -Seconds 5
$window.Close()