В предыдущем посте я показал, как можно легко и удобно подписывать скрипты и как усилить безопасность их исполнения, а так же предупредить несанкционированное изменение кода. Но, как я уже отметил, подписывать каждый раз скрипт из консоли не особо удобно. Например, если вы занимаетесь финальной отладкой скрипта в редакторе, как PowerGUI или PowerShell ISE, то в конце отладки вы просто сохраняете скрипт и закрываете редактор. Понятно, что ещё отдельно запускать консоль PowerShell будет неудобно. Поэтому я написал небольшой скрипт с установщиком, который позволит из проводника по правому нажатию на PS1 файл вы сможете подписывать скрипты.
Чтобы поместить свой элемент в контекстное меню при нажатии на PS1 файлы (на других типах файлов его не будет) нам нужно добавить некоторые значения в реестр по пути:
HKLM\Software\Classes\Microsoft.PowerShellScript.1\Shell
подключ с названием нашего элемента – Sign It и в нём уже команду, которая будет отрабатываться при нажатии на него. Я решил не изобретать велосипед и воспользовался приёмами, которые использовал ещё в старом блоге: Полезная безделушка Hash SHA1 на PowerShell. Шапка по сути осталась та же, только тело (исполняемый модуль) изменился. И вот, что получилось для PowerShell 1.0 (для V2 опубликую ниже):
##################################################################### # Sign PS1 Script.ps1 # Version 1.5 # # Automatically sign PowerShell script from .PS1 file context menu # Fully compatible with PowerShell 1.0 # # Vadims Podans (c) 2009 # http://www.sysadmins.lv/ ##################################################################### $FilePath = Read-Host "Specify path to hold SignIt.PS1" if (Test-Path $FilePath) { $FilePath = $FilePath + "\" + "SignIt.ps1" $RegPath = "Registry::HKLM\Software\Classes\Microsoft.PowerShellScript.1\Shell\Sign It\command" $RegValue = "C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -noninteractive -noprofile -command $FilePath '%1'" New-Item -Path $RegPath -Force -ErrorAction SilentlyContinue if (Test-Path $RegPath) { New-ItemProperty -Path $RegPath -Name "(Default)" -Value $RegValue New-Item -ItemType file -Path $FilePath -Force if (Test-Path $FilePath) { $exefile = 'param ($file) $cp = new-object Microsoft.CSharp.CSharpCodeProvider $cpar = New-Object System.CodeDom.Compiler.CompilerParameters $HideWindow = 0x0080 $ShowWindow = 0x0040 $Code = @" using System; using System.Runtime.InteropServices; namespace Win32API { public class Window { [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); } } "@ $cp.CompileAssemblyFromSource($cpar, $code) $PSHandle = (Get-Process –id $pid).MainWindowHandle [Win32API.Window]::SetWindowPos($PSHandle, 0, 0, 0, 0, 0, $HideWindow) function _msgbox_ ($title, $text, $type = "None") { [void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms") $msg = [Windows.Forms.MessageBox]::Show($text, $title, [Windows.Forms.MessageBoxButtons]::ok, [Windows.Forms.MessageBoxIcon]::$type) } $cert = @(dir cert:\currentuser\my -codesigning)[0] if (!$cert) { _msgbox_ -title "Error" -text "Here is no valid signing certificate!" -type "Error" exit } $status = Set-AuthenticodeSignature "$file" $cert -TimestampServer "http://timestamp.verisign.com/scripts/timstamp.dll" if ($status.status -eq "Valid") { _msgbox_ -title "Success" -text "Script is now signed! Enjoy!" } else { _msgbox_ -title "Error" -text $status.StatusMessage -type "Error" } exit' Set-Content -Path $FilePath -Value $exefile &$filepath $filepath } else { Write-Warning "Unable to create file in: $FilePath. Path is incorrect or you haven't sufficient permissions" } } else { Write-Warning "Unable to create registry key. May be you haven't sufficient permissions to HKLM hive" } }
Данный скрипт является установщиком. Для реализации всех функций достаточно запустить этот скрипт и по требованию указать путь, где будет находиться рабочий скрипт (содержимое переменной $exefile).
Примечание: такой метод установки справедлив только для V2. При использовании PowerShell 1.0 установщик следует запускать непосредственно из консоли PowerShell.
Заметка: Здесь следует обратить внимание на блок кода, который идёт после PARAM в переменной $exefile. Данный блок кода скрывает саму консоль PowerShell и показывает только графическую часть, которая исполняется из скрипта. Функция скрытия консоли честно взята из блога Васи Гусева: Win32 API из PowerShell 1.0. Данный метод работает как в PowerShell V2 CTP3, так и в PowerShell 1.0.
И вариант скрипта для PowerShell V2:
##################################################################### # Sign PS1 Script.ps1 # Version 1.5 # # Automatically sign PowerShell script from .PS1 file context menu # # Require PowerShell V2, not compatible with PowerShell 1.0 # # Vadims Podans (c) 2009 # http://www.sysadmins.lv/ ##################################################################### #requires -Version 2.0 $FilePath = Read-Host "Specify path to hold SignIt.PS1" if (Test-Path $FilePath) { $FilePath = $FilePath + "\" + "SignIt.ps1" $RegPath = "Registry::HKLM\Software\Classes\Microsoft.PowerShellScript.1\Shell\Sign It\command" $RegValue = "C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -WindowStyle Hidden -noprofile -command $FilePath '%1'" New-Item -Path $RegPath -Force -ErrorAction SilentlyContinue if (Test-Path $RegPath) { New-ItemProperty -Path $RegPath -Name "(Default)" -Value $RegValue New-Item -ItemType file -Path $FilePath -Force if (Test-Path $FilePath) { $exefile = 'param ($file) function _msgbox_ ($title, $text, $type = "None") { [void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms") $msg = [Windows.Forms.MessageBox]::Show($text, $title, [Windows.Forms.MessageBoxButtons]::ok, [Windows.Forms.MessageBoxIcon]::$type) } $cert = @(dir cert:\currentuser\my -codesigning)[0] if (!$cert) { _msgbox_ -title "Error" -text "Here is no valid signing certificate!" -type "Error" exit } $status = Set-AuthenticodeSignature "$file" $cert -TimestampServer "http://timestamp.verisign.com/scripts/timstamp.dll" if ($status.status -eq "Valid") { _msgbox_ -title "Success" -text "Script is now signed! Enjoy!" } else { _msgbox_ -title "Error" -text $status.StatusMessage -type "Error" } exit' Set-Content -Path $FilePath -Value $exefile &$filepath $filepath } else { Write-Warning "Unable to create file in: $FilePath. Path is incorrect or you haven't sufficient permissions" } } else { Write-Warning "Unable to create registry key. May be you haven't sufficient permissions to HKLM hive" } }
Так же, после копирования файла установщик попытается сразу подписать рабочий скрипт, чтобы этого не пришлось делать потом вручную. После этого в контекстном меню PS1 файлов будет элемент Sign It:
И если у пользователя есть сертификат для подписи скриптов, то при нажатии на этот элемент получите сообщение:
В противном случае получите ошибку:
Вот так можно значимо упростить процесс подписывания скриптов не открывая консоль PowerShell. Но если файлов будет много, то ничего не мешает основную часть кода засунуть в цикл Foreach. Например:
$cert = @(dir cert:\currentuser\my -codesigning)[0] dir -Include *.ps1 -Recurse | %{Set-AuthenticodeSignature "$_" $cert}
На этом пока о подписывании скриптов всё, что я хотел рассказать.
Update 30.08.2009: немного переделал скрипты с учётом следующих поправок:
И, собственно, кнопки для скачивания скриптов:
Спасибо огромное!!!
Как мне изменить скрипт что бы я мог за один присест подписать сразу большое количество файлов? Причем для меня желательно выделить необходимые файлы мышкой нажать пкм, выбрать "Подписать" и запустить подпись скопом. Хочу передавать в SignIt.ps1 не по одному файлу а сразу массив. Прошу помощи.
Вадим, $cert = @(dir cert:\currentuser\my -codesigning)[0] а ежели у меня там больше одного сертификата лежит? не лучше ли будет выяснить что там конкретно и потом $cert = Get-ChildItem cert:\CurrentUser\My -codesigning |where {$_.Subject -eq "CN=..."} ?
Можно и выяснять, например, через X509Certificate2UI показывать окошко, где пользователь выберет нужный сертификат. На момент написания поста у меня не было такой задачи.
Comments: