Contents of this directory is archived and no longer updated.

Давненько я ничего не постил в блог. Причины разные, это и проблема отыскать интересный материал для блога и лень. Но сегодня нашёл что написать. Это портированный скрипт с VBS для извлечения установочного ключа Windows и других продуктов, как Microsoft Office.

итак, исходный вариант на VBS, который был найден на просторах интернета:

Set WshShell = WScript.CreateObject("WScript.Shell") 
strDigitalProductId="HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DigitalProductId" 
strXPKey=GetKey(WshShell.RegRead(strDigitalProductId)) 
MsgBox "Key:"&strXPKey 
Function GetKey(rpk) 
    Const rpkOffset=52:i=28 
    szPossibleChars="BCDFGHJKMPQRTVWXY2346789" 
    Do 'Rep1 
        dwAccumulator=0 : j=14 
        Do  
            dwAccumulator=dwAccumulator*256  
            dwAccumulator=rpk(j+rpkOffset)+dwAccumulator 
            rpk(j+rpkOffset)=(dwAccumulator\24) and 255 
            dwAccumulator=dwAccumulator Mod 24 
            j=j-1 
        Loop While j>=0 
    i=i-1 : szProductKey=mid(szPossibleChars,dwAccumulator+1,1)&szProductKey 
    if (((29-i) Mod 6)=0) and (i<>-1) then  
    i=i-1 : szProductKey="-"&szProductKey 
    End If 
    Loop While i>=0 'Goto Rep1 
GetKey=szProductKey 
End Function

Учитывая мои почти нулевые познания в VBS пришлось сильно повозиться, чтобы понять его работу в VBS и активно сотрудничать с гуглом.

И столкнулся с некоторыми трудностями:

rpk(j+rpkOffset)=(dwAccumulator\24) and 255

вот здесь происходит деление. Но не простое деление, а деление с получением целого числа от частного с отрезанием десятичной части без округления. Возможно в VBS правила округления стандартные, но в PowerShell округление происходит несколько иначе, если у нас число с половинкой, например: 1,5; 5,5; 67,5. В таких случаях PowerShell округляет число до ближайшего чётного числа. Примеры:

PS C:\> [math]::Round(0.5)
0
PS C:\> [math]::Round(1.5)
2
PS C:\> [math]::Round(2.5)
2
PS C:\> [math]::Round(3.5)
4
PS C:\> [math]::Round(4.5)
4
PS C:\> [math]::Round(5.5)
6
PS C:\> [math]::Round(6.5)
6
PS C:\> [math]::Round(7.5)
8
PS C:\> [math]::Round(8.5)
8
PS C:\>

Как видите с использованием математической функции округления вы никогда не получите нечётное число, если округлять такие числа. Есть ещё вариант форматирования строки. Но он при отбрасывании дробной части округляет половинки до бОльшего целого числа (хотя нужно до меньшего, что и делает VBS в данном случае):

PS C:\> (0.5).tostring("F00")
1
PS C:\> (1.5).tostring("F00")
2
PS C:\> (2.5).tostring("F00")
3
PS C:\>] (3.5).tostring("F00")
4
PS C:\> (4.5).tostring("F00")
5
PS C:\> (5.5).tostring("F00")
6
PS C:\>

ну и я не нашёл ничего лучше, как написать простенький регексп и им отрезать ненужный хвостик. Суть регекспа будет заключаться в извлечении из строки только цифр начиная от начала строки до первого нечислового символа. Как я уже писал в предыдущем блоге в регулярных выражениях начало строки обозначается знаком возведения в степень – ^. Только числа - \d. Т.к. число может состоять из множества цифр, то я добавил символ умножения – *, чтобы сказать: от нуля и более символов. Получилось вот так:

PS C:\> 3456.356345 -match "^\d*"
True
PS C:\> $matches

Name                           Value
----                           -----
0                              3456


PS C:\> $matches[0]
3456
PS C:\>

я применил свой шаблон к произвольному дробному числу и оно попало под шаблон. Используя особую переменную $matches, которая в виде массива хранит все совпадения оператора –match, я могу извлекать ту часть сроки, которая попала под шаблон. Т.к. $matches у нас массив, то для вставки совпавшей части в код я буду обращаться к нужному элементу этого массива. В моём случае совпадение только одно, поэтому $matches будет массивом из единственного элемента и обращаться к его значению нужно по первому индексу, т.е. $matches[0].

Далее, мы видим оператор And. Здесь я тоже попал в засаду, поскольку подумал, что это аналог -AND в PowerShell. Однако после чтения документации выяснилось, что это Bitwise AND, т.е. побитовый “И”. Следовательно при портировании необходимо использовать не –AND, а –BAND. Разница у них в том, что оба числа преобразовываются в двоичный формат и побитово выполняется операция логического умножения. Подробности здесь: http://msdn.microsoft.com/en-us/library/z0zec0b2(VS.71).aspx.

szProductKey=mid(szPossibleChars,dwAccumulator+1,1)&szProductKey

оператор MID в VBS (как и в древнем досовском бейсике) вырезает часть строки и является аналогом метода Substring(), который есть в PowerShell. Однако тут я жестоко попался на следующее: в PowerShell любая нумерация индексов и символов начиная с нуля. Т.е. чтобы извлечь только первый символ из строки мне нужно указать стартовый индекс ноль - 0:

PS C:\> "here string".substring(0,1)
h
PS C:\>

но в VBS символы в строках нумеруются начиная с единицы – 1. Те. для моего примера чтобы вырезать первый символ строки в качестве стартовой позиции нужно было указывать не 0, а 1:

string = "here string"
msgbox mid(string,1,1)

Следовательно при портировании скрипта из вышеупомянутой строки убираем  “+1”, который, видимо, был добавлен ввиду особенности нумерации символов строки в VBS. Для кого-то это может показаться пустяком, но для меня это всё было открытием, поскольку я на VBS не написал ни одного скрипта (даже который просто мапит шару при логоне) и его синтаксис для меня достаточно сложен и не всегда понятен.

В остальном же я ограничился построчной трансляцией кода без каких-либо изменений с использованием аналогов PowerShell, поскольку я до конца и не разобрал алгоритм шифрования ключа. На выходе у меня получился вот такой скрипт:

########################################################
# Get-ProductKey.ps1
# Version 1.0
#
# Retrieve Windows XP/2003/Vista/2008 setup key#
# Note: not works on Windows Vista/2008 activated by KMS Server
# Note: translated from VBS script#
# Vadims Podans (c) 2009
# http://www.sysadmins.lv/
########################################################

function Get-ProductKey {
    $rpk = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\" -Name DigitalProductId).DigitalProductId
    $i = 28
    $rpkOffset = 52
    $PossibleChars = "BCDFGHJKMPQRTVWXY2346789"
    do {
        $Accumulator = 0
        $j = 14
        do {
            $Accumulator = $Accumulator * 256
            $Accumulator = $rpk[$j + $rpkOffset] + $Accumulator
            $Accumulator / 24 -match "^\d*" | Out-Null
            $rpk[$j + $rpkOffset] = $matches[0] -band 255
            $Accumulator = $Accumulator % 24
            $j--
            } while ($j -ge 0)
        $i--
        $ProductKey = $PossibleChars.Substring($Accumulator, 1) + $ProductKey
        if ((29 - $i) % 6 -eq 0 -and $i -ne -1) {
            $i--
            $ProductKey = "-" + $ProductKey
        }
    } while ($i -ge 0)
$ProductKey
}

Данный скрипт не будет возвращать установочный ключ для VLK систем Windows Vista и Windows Server 2008, которые были активированы через KMS сервер. Здесь ключ будет показан только для OEM и коробочных версий. На сколько я знаю в данном случае (как у меня на нотебуке с Vista Business) при активации через KMS ключ не хранится в системе и скрипт будет возвращать буквы B:

PS C:\> Get-ProductKey
BBBBB-BBBBB-BBBBB-BBBBB-BBBBB
PS C:\>

объяснение этому здесь: http://www.rjlsoftware.com/support/faq/sa.cfm?q=289&n=81

з.ы. ссылка не показывается в IE8 RC1, только верхний баннер :( Поэтому пришлось совершить грех и воспользоваться мозиллой :-)

Так же можно получать установочные ключи не только для самой ОС, но и для приложений тоже (разработки Microsoft, разумеется), как Microsoft Office. Для этого достаточно изменить ветку реестра на:

HKLM\Software\Microsoft\Office\11.0\Registration\{GUID}\DigitalProductID

Этот путь будет справедлив для Office 2003. Где хранит этот ProductID Office 2007 я не нашёл у себя. Но вообще мысль понятна, в каком направлении искать :-)

Удачи!


Share this article:

Comments:

Vadims Podāns

мне сегодня на почту Aleksander Ignatenko прислал вариант скрипта с использованием WMI, который позволяет получать установочные ключи с удалённых машин. Вот сам скрипт: function Get-ProductKey { param ($strComputer) $hklm = 2147483650 $key = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\" $value = "DigitalProductId" $wmi2 = "\\"+$strComputer+"\root\default:stdRegProv" $wmi = [wmiclass]$wmi2 $rpk = ($wmi.GetBinaryValue($hklm,$key,$value)).uValue # $rpk = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\" -Name DigitalProductId).DigitalProductId $i = 28 $rpkOffset = 52 $PossibleChars = "BCDFGHJKMPQRTVWXY2346789" do { $Accumulator = 0 $j = 14 do { $Accumulator = $Accumulator * 256 $Accumulator = $rpk[$j + $rpkOffset] + $Accumulator $Accumulator / 24 -match "^\d*" | Out-Null $rpk[$j + $rpkOffset] = $matches[0] -band 255 $Accumulator = $Accumulator % 24 $j-- } while ($j -ge 0) $i-- $ProductKey = $PossibleChars.Substring($Accumulator, 1) + $ProductKey if ((29 - $i) % 6 -eq 0 -and $i -ne -1) { $i-- $ProductKey = "-" + $ProductKey } } while ($i -ge 0) $ProductKey }

Comments are closed.