用 PowerShell 解析 eD2k 链接

电骡的 eD2k 链接包含了丰富的信息。例如这个:

ed2k://|file|BingPinyinSetup_1.5.24.02.exe|31485072|C8C9282E6112455E624EE82941E5BA00|p=79A822E1788353E0B289D2ADD5DA3BDE:FB9BB40DEDB1D2307E9D734A6416704B:0732B122C4ECF70065B181C92BF72400:437958DF590D764DE1694F91AC085225|h=HLXRQSANEO5MHIVOYNM5FNQOHJG3D5MP|s=http://blog.vichamp.com|s=http://www.baidu.com|/|sources,127.0.0.1:1234,192.168.1.1:8888|/

这给我们的第一感觉是可以用正则表达式来解析。我们观察一下它的规律,发现它是用 | 分割的字符串:

ed2k://
file
BingPinyinSetup_1.5.24.02.exe
31485072
C8C9282E6112455E624EE82941E5BA00
p=79A822E1788353E0B289D2ADD5DA3BDE:FB9BB40DEDB1D2307E9D734A6416704B:0732B122C4ECF70065B181C92BF72400:437958DF590D764DE1694F91AC085225
h=HLXRQSANEO5MHIVOYNM5FNQOHJG3D5MP
s=http://www.abc.com/def.zip
s=http://www.vichamp.com/qq.zip
/
sources,127.0.0.1:1234,192.168.1.1:8888
/

还有一些规律:

  • p= 开始,后面的段都是可选的。
  • p=xxxh=xxxs=xxx看起来像键值对。
  • s= 可以有多个,sources 后面的 IP 和端口可以有多对。

根据这个规律,我们可以很容易地构造出正则表达式,并用 PowerShell 解析它。

function Get-Ed2kLink {
    Param(
        [string]
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = 'Enter an ed2k:// url')]
        $Link
    )

    $regex = [regex]@'
(?x)
\bed2k://
\|file\|(?<FILE_NAME>[^|]+)
\|(?<FILE_SIZE>\d+)
\|(?<FILE_HASH>[0-9a-fA-F]+)
(?:\|p=(?:(?<HASH_SET>[0-9a-fA-F]+):?)+)?
(?:\|h=(?<ROOT_HASH>[0-9a-zA-Z]+))?
(?:\|s=(?<HTTP_SOURCE>[^|]+))*
\|\/
\|sources(?:,(?<SOURCES_HOST>[0-9a-zA-Z.]+):(?<SOURCES_PORT>\d+))*
|\/\b
'@
    $match = $regex.Match($Link)
    if ($match.Success) {
        $sourcesHost = $match.Groups['SOURCES_HOST'].Captures | Select-Object -ExpandProperty Value
        $sourcesPort = $match.Groups['SOURCES_PORT'].Captures | Select-Object -ExpandProperty Value
        $sources = @()
        for ($i = 0; $i -lt $sourcesHost.Length; $i++) {
            $sources += [PSCustomObject][Ordered]@{
                Host = $sourcesHost[$i]
                Port = $sourcesPort[$i]
            }
        }

        $result = [PSCustomObject][Ordered]@{
            File = $match.Groups['FILE_NAME'].Value;
            FileSize = $match.Groups['FILE_SIZE'].Value;
            FileHash = $match.Groups['FILE_HASH'].Value;
            HashSet = $match.Groups['HASH_SET'].Captures | Select-Object -ExpandProperty Value
            RootHash = $match.Groups['ROOT_HASH'].Value;
            HttpSource = $match.Groups['HTTP_SOURCE'].Captures | Select-Object -ExpandProperty Value
            Sources = $sources;
        }
    } else {
        $result = $null
    }

    return $result
}

Get-Ed2kLink 'ed2k://|file|BingPinyinSetup_1.5.24.02.exe|31485072|C8C9282E6112455E624EE82941E5BA00|p=79A822E1788353E0B289D2ADD5DA3BDE:FB9BB40DEDB1D2307E9D734A6416704B:0732B122C4ECF70065B181C92BF72400:437958DF590D764DE1694F91AC085225|h=HLXRQSANEO5MHIVOYNM5FNQOHJG3D5MP|s=http://www.abc.com/def.zip|s=http://www.vichamp.com/qq.zip|/|sources,127.0.0.1:1234,192.168.1.1:8888|/'

执行结果如下:

File       : BingPinyinSetup_1.5.24.02.exe
FileSize   : 31485072
FileHash   : C8C9282E6112455E624EE82941E5BA00
HashSet    : {79A822E1788353E0B289D2ADD5DA3BDE, FB9BB40DEDB1D2307E9D734A6416704B, 0732B122C4ECF70065B181C92BF72400, 437958DF590D764DE1694F91AC085225}
RootHash   : HLXRQSANEO5MHIVOYNM5FNQOHJG3D5MP
HttpSource : {http://www.abc.com/def.zip, http://www.vichamp.com/qq.zip}
Sources    : {@{Host=127.0.0.1; Port=1234}, @{Host=192.168.1.1; Port=8888}}

注意一下,由于 s=sources 节包含循环体,所以不能直接用 PowerShell 的 -cmatch 表达式和 $Matches 变量,必须用 .NET 的 [regex] 类来处理。

参考材料:

您也可以在这里下载完整的源代码。

PowerShell 技能连载 - 用正则表达式搜索文件

适用于 PowerShell 所有版本

Get-ChildItem 不支持高级的文件过滤。当您使用简单的通配符时,无法利用上正则表达式。

要用上正则表达式,需要增加一个过滤用的 cmdlet 和 -match 操作符。

这个例子将在 Windows 目录中查找所有文件名包含至少 2 位数字,且文件名不超过 8 个字符的文件:

Get-ChildItem -Path $env:windir -Recurse -ErrorAction SilentlyContinue |
  Where-Object { $_.BaseName -match '\d{2}' -and $_.Name.Length -le 8 }

请注意 BaseName 属性的使用。它只返回主文件名(不包含扩展名)。通过这种方式,扩展名中的数字不会被包含在内。

PowerShell 技能连载 - 获取指定扩展名的文件

适用于 PowerShell 所有版本

当您使用 Get-ChildItem 来获取一个文件列表时,您可能会注意到 -Filter 参数有时候会导致返回比你预期的更多的文件。

以下是一个例子。这段代码并不只是返回“.ps1”扩展名的文件,而也会返回“.ps1xml”扩展名的文件:

Get-ChildItem -Path C:\windows -Recurse -ErrorAction SilentlyContinue -Filter *.ps1

要限制只返回您需要的扩展名的文件,请用一个 cmdlet 来过滤结果:

Get-ChildItem -Path C:\windows -Recurse -ErrorAction SilentlyContinue -Filter *.ps1 |
  Where-Object { $_.Extension -eq '.ps1' }

这将只返回您指定的扩展名的文件。

PowerShell 技能连载 - 修正 ISE 的编码

适用于所有 PowerShell 版本

当您在 ISE 编辑器中运行一个控制台程序时,非标准字符,例如“ä”或“ß”将会显示不正常。要修正 ISE 和隐藏的控制台之间通信的编码,请使用这段代码:

# Repair encoding. This REQUIRES a console app to run first because only
# then will ISE actually create its hidden background console

$null = cmd.exe /c echo
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

# Now all is fine

cmd.exe /c echo ÄÖÜäöüß

PowerShell 技能连载 - 使用“打开文件”对话框

适用于 PowerShell 3.0 及以上版本

以下是一个快捷的函数,可以用在 ISE 编辑器和 PowerShell 控制台中(适用于 PowerShell 3.0 及以上版本):Show-OpenFileDialog

function Show-OpenFileDialog
{
  param
  (
    $StartFolder = [Environment]::GetFolderPath('MyDocuments'),

    $Title = 'Open what?',

    $Filter = 'All|*.*|Scripts|*.ps1|Texts|*.txt|Logs|*.log'
  )


  Add-Type -AssemblyName PresentationFramework

  $dialog = New-Object -TypeName Microsoft.Win32.OpenFileDialog


  $dialog.Title = $Title
  $dialog.InitialDirectory = $StartFolder
  $dialog.Filter = $Filter


  $resultat = $dialog.ShowDialog()
  if ($resultat -eq $true)
  {
    $dialog.FileName
  }
}

这个函数将打开一个“打开文件”对话框。用户可以选择一个文件,并且选择的文件对象将返回给 PowerShell。所以下次您的脚本需要打开一个 CSV 文件时,您可能就能用上。

PowerShell 技能连载 - 用 Group-Object 来创建哈希表

适用于所有 PowerShell 版本

Group-Object 能把对象输送到管道中,然后在一个管道中把属性相同的对象排在一起。

这个功能十分有用,特别是当您用 Group-Object 来返回哈希表时。它将生成一个按服务状态分组的哈希表:

$hash = Get-Service |
  Group-Object -Property Status -AsHashTable -AsString

您现在可以通过这种方式获取所有正在运行(或已停止的)服务:

$hash.Running
$hash.Stopped

可以用任何想要的属性来分组。这个例子将用三个组来分组文件:一组为小文件,一个组为中等文件,另一个组位大文件。

$code =
{
  if ($_.Length -gt 1MB)
  {'huge'}
  elseif ($_.Length -gt 10KB)
  {'average'}
  else
  {'tiny'}
}

$hash = Get-ChildItem -Path c:\windows |
  Group-Object -Property $code -AsHashTable -AsString


#$hash.Tiny
$hash.Huge

PowerShell 技能连载 - 用 PowerShell 来励志

适用于所有 PowerShell 版本

编写 PowerShell 代码是十分带劲的,但是某些时候会令人感到沮丧。这是一个用 PowerShell 来励志的函数。只需要打开音箱,PowerShell 会在您执行每一条命令之后鼓励你。

function prompt
{
  $text = 'You are great!', 'Hero!', 'What a checker you are.', 'Champ, well done!', 'Man, you are good!', 'Guru stuff I would say.', 'You are magic!'
  'PS> '
  $host.UI.RawUI.WindowTitle = Get-Location
  (New-Object -ComObject Sapi.SpVoice).Speak(($text | Get-Random))
}

PowerShell 技能连载 - 记录脚本做了什么事

适用于所有 PowerShell 版本

您应该知道在 PowerShell 控制台(不是 ISE 编辑器)中,您可以打开记录功能:

PS> Start-Transcript

这将会把所有键入的命令以及所有的命令执行结果都记录到一个文件中。不幸的是,当您运行一个脚本的时候,作用就受限了,因为您无法看到实际的脚本命令。

以下是一个激进的技巧,能够记录包括所有脚本中执行的命令。在您尝试这个技巧之前,请注意这将增加您的日志文件大小并且会导致脚本执行变慢,因为在循环体中,每一次循环都会被记录下来。

只要执行这行代码就可以打开脚本命令记录了:

PS> Set-PSDebug -Trace 1

用 PowerShell 生成随机身份信息

在开发、运维等工作中,我们常常需要生成一些随机的身份信息,例如“张三”、“李四”……等。实际中不光需要姓名,最好还有邮箱、QQ、联系电话、身份证号等。我们可以一劳永逸地写一个 PowerShell 脚本,随时优雅地生成一大串随机身份信息,取之不尽用之不竭。使用效果如下(当然也可以用 Export-Csv 轻松导出到 Excel 或者用 ConvertTo-Html 生成网页):

根据 *PowerShell 技术交流 QQ 群今天的讨论,以及 shrekzpowershell 生成随机用户信息,我做了一些改进。增加了生日、性别、身份证号等。设计要点如下:

  • QQ 号和邮箱须对应。
    身份证号的第 1-6 位是地区号码,应符合 G​B​T​2​2​6​0​—​1​9​9​9 规范。
  • 身份证号的第 7-13 位是生日号码,不能和同一个人的生日号码矛盾。
  • 身份证号的第 17 位,对于男性是奇数,对于女性是偶数,不能和同一个人的性别矛盾。
  • 身份证号的第 18 位是校验位,根据 ISO 7064:1983.MOD 11-2 规范计算。
  • 生成身份证号时,既可以指定生日号码和性别,也可以随机生成。

以下是一个可重用的模块 Get-Identity.psm1,可以把它保存在 %PSModulePath% 目录中,随时调用:

$firstNames = (@'
赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章
云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常
乐于时傅皮卞齐康伍余元卜顾孟平黄和穆萧尹
'@.Split(("`n", "`r")) -join $null).ToCharArray()

$secondNames = (@'
一丁三专世业丝中丰临丹丽举乃义乐乔书云亘亮人仁今仙令仪伟伯伶佑作佩佳侠侬俊俏俐信
修倩健偲儿允元兆光兰兴其典军冠冬冰凌凝凡凯刚初利力勃勇勋化北千卉华卓博卫卿厚原友
双发叡可叶司合吉同名向君吟听启周和咏咸品哲唱善喆喜嗣嘉囡国圣坚基堂墨壁夏多天奇奕
奥好如妃妍妙妞妮姗姝姣娅娇娜娟娥娴婉婧婵婷媚媛嫒嫔嫚子存孟季学宁宇安宜宝实宣宵家
宸容宾密寒寰寻寿小尘尚尹展山岑岚峯峰峻巍州工巧布帅帆希干平年广庆康庸延建弘强弼彤
彦彩彬彭影微德心志忠念忻怀思怡恩恬恺悟悦情惜惠愉意慈慕慧懋懿成才扬承抒拔振捷掣敏
教文斌斯新方施旎旭旻昂昊昌明易昕星春昶晋晓晔晖晤晨景晴晶智暄暖暮曜曦曲曼曾月朋朔
朗望朝木本朵杉杏材杰松林枝枫柏柔柳栋树格桃桐梅梓梦棠森楚楠欢欣歆歌正武毅民水永江
池沈沉沙沛河泉波泰泽洁洛津洮洲流济浓浦浩海涉涛润涵淑淳淼清渊温湃湉湘源溥溪滢滨漪
漫澄澍澹濮濯瀚灵灿炎炳烁烨焕焱然煜煦熙熠燕爽牧献玄玉玑玛玟玲珉珊珍珑珠珺琅琇琛琦
琨琪琲琳琴琼瑜瑞瑶瑾璇璞瓃甜用甫田甲男画畅略白皎益盼真睿知石碧磊礼祖祥祯祺禄福禧
禾秀秉秋程穆空立章童端竹笑笛筠简箫籁米精素红纬纳纵纶经绢绣绮维罗罡美羡羽翎翔翠翮
翰翼耀聪胜能自致舒舟航良艳艺艾芃芊芝芦芬花芳芸苑苗若英茂范茉茗茜茵荌荣荫莉莎莘莲
莹菁菊菡菱菲萌萍萝萧萱蒙蓉蓓蓝蔓蔚蕊蕙蕴蕾薄薇藉藻虎虹蝶行衣裕西言誉许识诗诚语诺
谊谧谷豪贝贞贤资赋赐赡赫超越跃路轩载辉辰达迈运进远迪逸邈邵郁郎采金鑫铃铄锋锐锦长
闲闵阳阵陶隽雄雅雨雪雯雰霁霓霖霞露青靓靖静韦音韵韶顺颀颖颜飇风飙飞香馨驰驹骄高魁
魄鲲鲸鸣鸾鸿鹍鹏麦默黛齐龙
'@.Split(("`n", "`r")) -join $null).ToCharArray()

$mobilePrefixes = @'
134 135 136 137 138 139 147 150 151 152 157 158 159 182 187 188 147 157 188
130 131 132 155 156 185 186 186 133 153 180 189 189 200 133 150 151 152 153
155 156 157 158 159 130 131 132 133 134 135 136 137 138 139 180 182 185 186
187 188 189 170
'@.Split(("`n", "`r", " "), 'RemoveEmptyEntries')

$cityCodes = @'
410701 341101 130901 130601 331001 230501 370601 659001 450301 120221 620701
341001 210201 510101 130501 320901 520401 510701 623001 450401 650201 420901
360701 620401 510501 542521 620501 232701 530401 611001 340401 321301 520101
320401 430501 653101 460101 220301 640201 610801 440501 450701 533421 429004
522401 532901 532801 130101 652101 445301 320601 410301 140201 530501 632121
511301 210501 152921 140901 330701 410101 350501 621101 350901 652801 652201
210801 440801 341801 500101 371401 411301 341501 141101 371301 440301 522201
231101 510901 330601 450201 350301 150201 220101 440901 411601 371101 320101
632221 330301 622901 130201 140101 131001 430301 610701 640101 450801 360601
131101 650101 440101 210301 632801 360401 231001 320801 530601 610201 210701
632321 360501 440201 512001 542621 370901 320201 220501 230801 220401 110228
430401 451201 340701 542301 130401 360201 410901 620601 150301 150801 140401
451001 532501 370101 440601 321001 411701 321101 654002 451401 410501 220801
350401 330201 522301 130701 420301 445101 420701 421301 211201 511701 420601
'@.Split(("`n", "`r", " "), 'RemoveEmptyEntries')

function Get-RandomName {
    return ($firstNames|Get-Random) +
    (($secondNames|Get-Random -Count ((1,2)|Get-Random)) -join $null)
}

function Get-RandomQQ {
    return [string](Get-Random -Minimum 100000 -Maximum 9999999999)
}

function Get-RandomEMail ($QQ) {
    if ($QQ) {
        return "$QQ@qq.com"
    } else {
        return (Get-RandomQQ) + '@qq.com'
    }
}

function Get-RandomMobile {
    return ($mobilePrefixes | Get-Random) +
    ((0..9 | Get-Random -count 8) -join $null)
}

function Get-RandomSex {
    return ('男', '女' | Get-Random)
}

function Get-RandomBirthday {
    return (Get-Date).AddDays(-(Get-Random -Maximum (365 * 110))).Date
}

function Get-RandomID  ([DateTime]$Birthday, $Sex){
    $cityCode = $cityCodes | Get-Random
    #$cityCode = (0..9 | Get-Random -Count 6) -join $null

    if (!$Birthday) {
        $Birthday = Get-RandomBirthday

    }
    $birthdayCode = '{0:yyyyMMdd}' -f $Birthday

    if ($Sex -eq '男' -or $Sex -eq $true) {
        $seq = (Get-Random -Minimum 0 -Maximum 49) * 2 + 1
    } else {
        $seq = (Get-Random -Minimum 0 -Maximum 49) * 2 + 2
    }

    $result = '{0}{1}{2:D3}' -f $cityCode, $birthdayCode, $seq
    $w = @(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2)
    $total = 0
    for ($i = 0; $i -lt 17; $i++) {
        $total += $w[$i] * [int]::parse($result[$i])
    }
    $checkCode = [string]($total % 11)
    $checkCode = '10X98765432'[$checkCode]

    $result = $result + $checkCode
    return $result
}

function Get-RandomIdentity {
    $qq = Get-RandomQQ
    $birthday = Get-RandomBirthday
    $sex = Get-RandomSex
    return [pscustomobject][ordered]@{
        Name = Get-RandomName;
        QQ = $qq;
        EMail = Get-RandomEMail -QQ $qq;
        Mobile = Get-RandomMobile;
        Birthday = '{0:yyyy/MM/dd}' -f $birthday
        Sex = $sex
        ID = Get-RandomID -Birthday $birthday -Sex $sex
    }
}

Export-ModuleMember *

测试代码 Test-GetIdentity.ps1

if (Get-Module Get-Identity) { Remove-Module Get-Identity }
Import-Module .\Get-Identity.psm1

Get-RandomName # 生成随机的姓名
Get-RandomQQ # 生成随机的 QQ 号
Get-RandomEMail # 生成随机的 e-mail
Get-RandomMobile # 生成随机的手机号
'{0:yyyy/MM/dd}' -f (Get-RandomBirthday) # 生成随机的生日
Get-RandomSex # 生成随机的性别
Get-RandomID # 生成随机的身份证号

Get-RandomIdentity # 生成随机的身份信息

# 批量生成 20 个完整的身份信息
1..20 | % {
    Get-RandomIdentity
} | Out-GridView -Title 随机身份信息

输出结果:

孔懿
9903344437
7351437013@qq.com
15248325719
1950/02/24
男
450301200801030764


Name     : 方盼
QQ       : 8679192225
EMail    : 8679192225@qq.com
Mobile   : 13923186497
Birthday : 2005/11/09
Sex      : 男
ID       : 130101200511090819

以及文章开头显示的那个图形化列表。

您也可以下载完整的 源代码测试脚本

另外如果您对如何提取区域代码感兴趣的话,还可以下载国标行政区划数据 GBT2260-1999.xml 和对应的解析程序 GBT2260.ps1 来研究。

PowerShell 技能连载 - 有趣的声音提示

适用于所有 PowerShell 版本

如果您的计算机装有声卡,那么这段代码可以让您的同事们吓一跳:

function prompt
{
  1..3 | ForEach-Object {
    $frequency = Get-Random -Minimum 400 -Maximum 10000
    $duration = Get-Random -Minimum 100 -Maximum 400
    [Console]::Beep($frequency, $duration)
  }
  'PS> '
  $host.ui.RawUI.WindowTitle = Get-Location
}

这段代码将会缩短您的 PowerShell 提示符,并且在标题栏上显示当前的路径。这还算是有益的功能。搞破坏的部分是每次执行一条命令,都会发出随机频率的刺耳的三连音:)。