Перейти к содержанию

Использование коммандлета Add-Type для вызова функции Windows API

Коммандлет Add-Type добавляет заданный .NET класс в текущий сеанс PowerShell. Данная статья демонстрирует использование этого командлета для доступа к функции CopyFile, объявленной в библиотеке kernel32.dll.

Базовая реализация

Рассмотрим скрипт, вызывающий функцию CopyFile:

PowerShell
$MethodDefinition = @'
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern bool CopyFile(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);
'@
$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -Namespace 'Win32' -PassThru
$Kernel32::CopyFile("$($Env:SystemRoot)\System32\calc.exe", "$($Env:USERPROFILE)\Desktop\calc.exe", $False)

Этот скрипт делает следующее:

  • Переменная $MethodDefinition содержит определение метода на C#, соответствующее сигнатуре CopyFile на C++:

    Разверните, чтобы увидеть подробности

    Сигнатура метода на C++
    BOOL CopyFile(
        [in] LPCTSTR lpExistingFileName,
        [in] LPCTSTR lpNewFileName,
        [in] BOOL    bFailIfExists
    );
    
    Определение метода на C#
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern bool CopyFile(
        string lpExistingFileName,
        string lpNewFileName,
        bool   bFailIfExists
    );
    

    Аттрибут DllImport указывает что метод CopyFile доступен в библиотеке kernel32.dll в качестве статической точки входа.

    Обратите внимание на корректность соответствия типов параметров в С++ и в .NET коде:

    Тип C/C++ Тип .NET
    BOOL bool
    LPCTSTR string
  • Командлет Add-Type создает класс Kernel32, содержащий метод CopyItem, определение которого задано на предыдущем шаге.

  • Последняя строка кода вызывает метод Kernel32::CopyFile для копирования файла calc.exe из папки Windows\System32 на рабочий стол.

Дальнейшие улучшения

  • Редактирование C# кода в строковой константе внутри PowerShell скрипта может оказаться неудобным. Вы можете сохранить C# код в отдельный файл и получить контент файла с помощью командлета Get-Content.
  • Вы можете объявить командлет Copy-RawItem в своем PowerShell модуле — чтобы облегчить повторное использование кода.
  • Вы можете реализовать обработку ошибок — генерировать исключение при неудачном завершении CopyFile.

Следующий ниже код демонстрирует реализацию модуля CopyRawItem.psm1 с учетом вышеизложенных доработок:

Модуль PowerShell
function Copy-RawItem {
    [CmdletBinding()]
    [OutputType([System.IO.FileSystemInfo])]
    Param (
        [Parameter(Mandatory = $True, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Path,
        [Parameter(Mandatory = $True, Position = 1)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Destination,
        [Switch]
        $FailIfExists
    )
    $MethodDefinition = Get-Content -Path .\CopyFile.cs
    $Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -Namespace 'Win32' -PassThru
    $CopyResult = $Kernel32::CopyFile($Path, $Destination, ([Bool] $PSBoundParameters['FailIfExists']))
    if ($CopyResult -eq $False) {
        throw New-Object ComponentModel.Win32Exception(
            [System.Runtime.InteropServices.Marshal]::GetLastWin32Error())
    }
    else {
        Write-Output (Get-ChildItem $Destination)
    }
}

Вы можете импортировать этот модуль и вызвать Copy-RawItem из вашего скрипта таким образом:

PowerShell
Import-Module ./CopyRawItem.psm1
Copy-RawItem \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\System32\config\SAM C:\temp\SAM -FailIfExists

В этом примере, файл базы данных диспетчера учётных записей безопасности копируется из теневой копии тома во временную папку (что невозможно при использовании стандартного командлета Copy-Item). Если запустить Copy-RawItem дважды, вы получите ошибку “The file exists”:

Copy-RawItem result

При задании неверного пути выдается ошибка “The system cannot find the path specified”.

Совет

Если вы получаете ошибку “Access is denied” при доступе к теневой копии, перезапустите сеанс PowerShell от имени администратора.

Примечание

Использованный здесь код доступен на GitHub: powershell-winapi-tutorial/examples/add-type.