Отладка скриптов PowerShell, выполняемых с помощью расширения пользовательских сценариев или команды запуска

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

Предварительные требования

Обзор

Предположим, что вы использовали расширение пользовательских скриптов или функцию запуска команды для запуска сценария PowerShell. Что делать в случае сбоя скрипта? Существует несколько доступных методов для определения причины сбоя.

PowerShell имеет несколько выходных потоков. Журналы для расширений пользовательских скриптов и скриптов запуска команд отправляют поток Успешно в StdOut подсостояние, а поток ошибок — в StdErr подсостояние. Эти вложенные значения относятся к расширению, которое использовалось для выполнения скрипта расширения пользовательского скрипта или скрипта запуска команды.

Подсостояние StdOut и StdErr находятся в представлении экземпляра точки регистрации сертификатов (CRP) для виртуальной машины. Эти подсостояние отображаются в нескольких расположениях согласно следующей таблице.

Интерфейс Как просмотреть подсостояние
Портал Azure
  1. Поиск для и выберите Виртуальные машины.
  2. Выберите виртуальную машину из списка.
  3. На странице обзора виртуальной машины выберите Расширения и расширения приложений>.
  4. Выберите расширение, которое использовалось для выполнения команды. (Он будет называться CustomScriptExtension или RunCommand.)
  5. Выберите Просмотреть подробное состояние.
Azure PowerShell Введите командлет Get-AzVM , чтобы получить свойства виртуальной машины Azure следующим образом:
Get-AzVM -ResourceGroupName <resource-group-name> -Name <vm-name> -Status
Azure CLI Введите команду az vm get-instance-view , чтобы получить сведения об экземпляре виртуальной машины Azure, как показано ниже.
az vm get-instance-view --resource-group <resource-group-name> --name <vm-name> --query instanceView.extensions

Ошибка, которая обычно приводит к сбою скрипта, отображается в StdErr подсостояние. Однако скрипты также могут завершаться сбоем без регистрации записи о неустранимой ошибке в этом подсостоявшемся состоянии.

Тестирование скрипта вручную и с помощью PsExec

Вручную убедитесь, что скрипт успешно выполняется из административной консоли PowerShell на виртуальной машине.

Если скрипт работает вручную, используйте PsExec для запуска скрипта с помощью локальной системной учетной записи. Для расширения пользовательских скриптов и команды выполнения скрипты выполняются с помощью этой учетной записи. Введя psexec -s, можно протестировать скрипт с помощью локальной системной учетной записи, но без использования расширения пользовательского скрипта или команды запуска. Если сбой воспроизводится с помощью psexec -s, то расширение пользовательских скриптов и команда запуска не являются причиной проблемы.

Тестирование с помощью PsExec

Вы можете использовать PsExec для удаленного запуска тестового скрипта PowerShell. Откройте административное окно командной строки и введите следующую команду PsExec. Замените заполнитель полным именем скрипта PowerShell:

psexec -accepteula -nobanner -s powershell.exe -NoLogo -NoProfile -File <C:\path\script-name.ps1>

Кроме того, вы можете использовать PsExec в интерактивном режиме. В следующем примере вы вводите команду whoami , чтобы показать, что PowerShell работает под учетной записью Локальной системы (NT AUTHORITY\SYSTEM).

C:\>psexec -accepteula -nobanner -s powershell.exe -NoLogo -NoProfile

PS C:\Windows\system32> whoami
nt authority\system 

Включение ведения журнала выполнения скрипта PowerShell

Если подсостояние StdErr не показывает причину проблемы, можно включить несколько типов ведения журнала, чтобы совместно отобразить содержимое и выходные данные скрипта. В этом журнале показано, что скрипт пытается выполнить, а также результат выполнения скрипта.

Чтобы включить различные типы ведения журнала, выполните действия, описанные в следующих разделах.

Предупреждение

Некоторые инструкции включают изменение реестра Windows. При неправильном изменении реестра с использованием редактора реестра или другого способа могут случиться серьезные проблемы. Для решения этих проблем может потребоваться переустановка операционной системы. Корпорация Майкрософт не может гарантировать, что эти проблемы удастся решить. Сначала создайте резервную копию существующих записей реестра, а затем измените реестр на свой страх и риск.

Увеличение максимального размера журналов событий

Большое количество событий можно создать как в журнале безопасности, так и в журнале событий Microsoft-Windows-PowerShell/Operational. Чтобы предотвратить потерю этих зарегистрированных событий, увеличьте максимальный размер журналов. Но если любой из этих журналов имеет максимальный размер 100 МБ или больше ( maxSize значение 104 857 600 или более), оставьте параметр максимального размера как есть.

Чтобы проверка максимальный размер журнала, используйте команду wevtutil и get-log параметр для получения сведений о журналах событий:

wevtutil get-log "Security"
wevtutil get-log "Microsoft-Windows-PowerShell/Operational"

Вы увидите выходные данные, похожие на приведенный ниже текст. В таких случаях максимальный размер журнала гораздо меньше 100 МБ.

name: Security
enabled: true
type: Admin
owningPublisher:
isolation: Custom
channelAccess: O:BAG:SYD:(A;;CCLCSDRCWDWO;;;SY)(A;;CCLC;;;BA)(A;;CC;;;ER)(A;;CC;;;NS)
logging:
  logFileName: %SystemRoot%\System32\Winevt\Logs\Security.evtx
  retention: false
  autoBackup: false
  maxSize: 20971520
publishing:
  fileMax: 1
name: Microsoft-Windows-PowerShell/Operational
enabled: true
type: Operational
owningPublisher: Microsoft-Windows-PowerShell
isolation: Application
channelAccess: O:BAG:SYD:(A;;0x2;;;S-1-15-2-1)(A;;0x2;;;S-1-15-3-1024-3153509613-960666767-3724611135-2725662640-12138253-543910227-1950414635-4190290187)(A;;0xf0007;;;SY)(A;;0x7;;;BA)(A;;0x7;;;SO)(A;;0x3;;;IU)(A;;0x3;;;SU)(A;;0x3;;;S-1-5-3)(A;;0x3;;;S-1-5-33)(A;;0x1;;;S-1-5-32-573)
logging:
  logFileName: %SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-PowerShell%4Operational.evtx
  retention: false
  autoBackup: false
  maxSize: 15728640
publishing:
  fileMax: 1

Чтобы увеличить максимальный размер журнала безопасности или журнала событий Microsoft-Windows-PowerShell/Operational до 100 МБ, выполните команду wevtutil вместе с параметром set-log :

wevtutil set-log "Security" /ms:104857600
wevtutil set-log "Microsoft-Windows-PowerShell/Operational" /ms:104857600

Включение аудита создания процесса

Используйте команду auditpol set , командлет New-Item и командлет New-ItemProperty , чтобы включить аудит создания процесса:

auditpol /set /category:"detailed tracking" /success:enable /subcategory:"Process Creation"

$registryPath = @{
    Path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit'
}
New-Item @registryPath

$forceDwordOne = @{
    PropertyType = 'DWord'
    Value = 1
    Force = $True
}
New-ItemProperty @registryPath -Name 'ProcessCreationIncludeCmdLine_Enabled' @forceDwordOne

Включение транскрибирования PowerShell

$registryPath = @{
    Path = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\Transcription'
    Force = $True
}
New-Item @registryPath

$dwordOne = @{
    PropertyType = 'DWord'
    Value = 1
}
New-ItemProperty @registryPath -Name 'EnableTranscripting' @dwordOne
New-ItemProperty @registryPath -Name 'EnableInvocationHeader' @dwordOne
New-ItemProperty @registryPath -Name 'OutputDirectory' -PropertyType 'String' -Value 'C:\Transcripts'

Включение ведения журнала модуля PowerShell

$moduleLoggingPath = @{
    Path = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ModuleLogging'
}
New-Item @moduleLoggingPath

$forceDwordOne = @{
    PropertyType = 'DWord'
    Value = 1
    Force = $True
}
New-ItemProperty @moduleLoggingPath -Name 'EnableModuleLogging' @forceDwordOne

$moduleNamesPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging\ModuleNames'
New-Item -Path $moduleNamesPath
New-ItemProperty -Path $moduleNamesPath -Name '*' -PropertyType String -Value '*'

Включение ведения журнала блоков сценариев PowerShell

$registryPath = @{
    Path = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging'
}
New-Item @registryPath

$forceDwordOne = @{
    PropertyType = 'DWord'
    Value = 1
    Force = $True
}
New-ItemProperty @registryPath -Name 'EnableScriptBlockLogging' @forceDwordOne

Общие сведения о выходных данных

При аудите создания процесса в журнал событий безопасности будут записываться события с идентификатором 4688 и событием 4689 . Событие с идентификатором 4688 предназначено для создания процесса и включает командную строку процесса. Событие с идентификатором 4689 предназначено для завершения процесса.

Транскрибирование создает текстовый файл в каталоге C:\Transcripts\<output-date> . Каталог будет создан автоматически, если он еще не существует.

Например:

C:\transcripts\20201211\PowerShell_transcript.<vm-name.a>+BWp8CT.20201211034929.txt

Ведение журнала модуля будет записывать событие с идентификатором 4103 в журнал событий Microsoft-Windows-PowerShell/Operational. Событие "4103" включает имя и выходные данные командлета.

При выполнении команды Write-Host $Env:ComputerNameв верхней части события с идентификатором 4013 отображается следующий текст, где value="<vm-name>" указывается, что выходными данными команды является имя виртуальной машины:

CommandInvocation(Write-Host): "Write-Host"
ParameterBinding(Write-Host): name="Object"; value="<vm-name>"

Ведение журнала блоков скриптов будет записывать событие с идентификатором 4104 в журнал событий Microsoft-Windows-PowerShell/Operational . События "4104" содержат содержимое скрипта. Скрипты, превышающие максимальный размер сообщения события, регистрируются как несколько событий "4104".

После включения ведения журнала и воспроизведения сбоя скрипта выполните следующий сценарий, чтобы экспортировать соответствующие события в файл с разделими-запятыми (CSV). Следующий запрос проверяет данные за предыдущие 24 часа (86 400 000 миллисекунда). Введите значение 3600000, чтобы получить только последний час. Значение 604800000 извлекает последнюю неделю.

$path = "PSEvents_$($env:COMPUTERNAME)_$(Get-Date ((Get-Date).ToUniversalTime()) -Format yyyyMMddHHmmss).csv"

$hours = 1 # Increase this to have it query more than just the last 1 hour
$now = Get-Date
$startTimeUtc = Get-Date ($now.AddHours(-$hours).ToUniversalTime()) -Format 'yyyy-MM-ddTHH:mm:ssZ'
$endTimeUtc = Get-Date ($now.ToUniversalTime()) -Format 'yyyy-MM-ddTHH:mm:ssZ'

$filterXML = @"
<QueryList>
    <Query Id="0" Path="Security">
        <Select Path="Security">
            Event
            [
                System
                [
                    (EventID = '4688' or EventID = '4689')
                    and
                    TimeCreated
                    [
                        @SystemTime &gt;= '$startTimeUtc'
                        and
                        @SystemTime &lt;= '$endTimeUtc'
                    ]
                ]
                and
                EventData
                [
                    Data[@Name="SubjectUserSid"] = "S-1-5-18"
                ]
            ]
        </Select>
    </Query>
    <Query Id="1" Path="Microsoft-Windows-PowerShell/Operational">
        <Select Path="Microsoft-Windows-PowerShell/Operational">
            Event
            [
                System
                [
                    (EventID ='4103' or EventID ='4104')
                    and
                    Security
                    [
                        @UserID ='S-1-5-18'
                    ]
                    and
                    TimeCreated
                    [
                        @SystemTime &gt;= '$startTimeUtc'
                        and
                        @SystemTime &lt;= '$endTimeUtc'
                    ]
                ]
            ]
        </Select>
    </Query>
</QueryList>
"@

$events = Get-WinEvent -FilterXml $filterXML | Sort-Object -Property RecordId
$events = $events | Select-Object -Property RecordId,
    TimeCreated, Id, MachineName, LogName, TaskDisplayName, Message
$events | Export-Csv -Path $path -NoTypeInformation

Отключение ведения журнала выполнения скрипта PowerShell

Чтобы отменить изменения, внесенные для включения ведения журнала сценариев PowerShell на виртуальной машине, выполните следующие действия.

  1. Если ранее вы увеличили максимальный размер журнала безопасности или журнала Microsoft-Windows-PowerShell/Operational, отменить изменения эти значения до максимальных размеров по умолчанию:

    wevtutil set-log "Security" /ms:20971520
    wevtutil set-log "Microsoft-Windows-PowerShell/Operational" /ms:15728640
    
  2. Отключите аудит создания процесса:

    auditpol /set /category:"detailed tracking" /success:disable /subcategory:"Process Creation"
    $commandSettings = @{
        Path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit'
        Name = 'ProcessCreationIncludeCmdLine_Enabled'
        PropertyType = 'DWord'
        Value = 0
        Force = $True
    }
    New-ItemProperty @commandSettings
    
  3. Отключите транскрибирование:

    $registryPath = @{
        Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription'
    }
    $forceDwordZero = @{
        PropertyType = 'DWord'
        Value = 0
        Force = $True
    }
    New-ItemProperty @registryPath -Name 'EnableTranscripting' @forceDwordZero
    New-ItemProperty @registryPath -Name 'EnableInvocationHeader' @forceDwordZero
    Remove-ItemProperty @registryPath -Name 'OutputDirectory'
    
  4. Отключите ведение журнала модуля:

    $moduleLoggingPath = @{
        Path = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ModuleLogging'
    }
    $forceDwordZero = @{
        PropertyType = 'DWord'
        Value = 0
        Force = $True
    }
    New-ItemProperty @moduleLoggingPath -Name 'EnableModuleLogging' @forceDwordZero
    
    $moduleNamesPath = @{
        Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging\ModuleNames'
    }
    Remove-ItemProperty @moduleNamesPath -Name '*'
    
  5. Отключите ведение журнала блоков скриптов:

    $registryPath = @{
        Path = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging'
    }
    $forceDwordZero = @{
        PropertyType = 'DWord'
        Value = 0
        Force = $True
    }
    New-ItemProperty @registryPath -Name 'EnableScriptBlockLogging' @forceDwordZero
    
  6. Удалите папку транскрибирования:

    Remove-Item -Path 'C:\Transcripts' -Force -Recurse
    
  7. Создайте резервную копию и очистите журнал безопасности и журнал Microsoft-Windows-PowerShell/Operational:

    wevtutil clear-log Security /bu:Security.evtx
    wevtutil clear-log Microsoft-Windows-PowerShell/Operational /bu:Microsoft-Windows-PowerShell_Operational.evtx
    

    Или очистите журнал безопасности и журнал Microsoft-Windows-PowerShell/Operational без резервного копирования:

    wevtutil clear-log Security
    wevtutil clear-log Microsoft-Windows-PowerShell/Operational
    

Ведение журнала команды тестового запуска на виртуальной машине

Скачайте тестовый скрипт Test-CustomScriptExtension.ps1 в текущий локальный каталог. Затем запустите сценарий на виртуальной машине с помощью командлета Invoke-AzVMRunCommand . Используйте свойства виртуальной машины, чтобы заменить заполнители для имени группы ресурсов и имени виртуальной машины.

$scriptUri = 'https://raw.githubusercontent.com/Azure/azure-support-scripts/master/Images_Extensions/PowerShell/Test-CustomScriptExtension.ps1'
$localFileLocation = "$PWD\Test-CustomScriptExtension.ps1"
(New-Object System.Net.WebClient).DownloadFile($scriptUri, $localFileLocation)

$commandSettings = @{
    ResourceGroupName = '<resource-group-name>'
    VMName = '<vm-name>'
    CommandId = 'RunPowerShellScript'
    ScriptPath = $localFileLocation
}
Invoke-AzVMRunCommand @commandSettings

Тестирование ведения журнала расширения пользовательских скриптов на виртуальной машине

Запустите скрипт теста Test-CustomScriptExtension.ps1 на виртуальной машине с помощью командлета Set-AzVMCustomScriptExtension . Используйте свойства виртуальной машины, чтобы заменить заполнители для имени группы ресурсов, имени виртуальной машины и расположения.

$commandSettings = @{
    ResourceGroupName = '<resource-group-name>'
    VMName = '<vm-name>'
    Name = 'CustomScriptExtension'
    FileUri = 'https://raw.githubusercontent.com/Azure/azure-support-scripts/master/Images_Extensions/PowerShell/Test-CustomScriptExtension.ps1'
    Run = 'Test-CustomScriptExtension.ps1'
    Location = '<azure-region-name-or-code>'
    ForceRerun = (Get-Date).Ticks
}
Set-AzVMCustomScriptExtension @commandSettings

Кроме того, этот тестовый скрипт можно запустить на виртуальной машине с помощью командлета Set-AzVMExtension . Необходимо указать ExtensionType параметр CustomScriptExtension вместе с несколькими другими изменениями параметров. Используйте свойства виртуальной машины, чтобы заменить заполнители для имени группы ресурсов, имени виртуальной машины и расположения.

$publicConfigSettings = @{
    'fileUris' = @('https://raw.githubusercontent.com/Azure/azure-support-scripts/master/Images_Extensions/PowerShell/Test-CustomScriptExtension.ps1')
    'commandToExecute' = 'powershell -File Test-CustomScriptExtension.ps1 -ExecutionPolicy Unrestricted'
}
$commandSettings = @{
    Publisher = 'Microsoft.Compute'
    ExtensionType = 'CustomScriptExtension'
    Settings = $publicConfigSettings
    ResourceGroupName = '<resource-group-name>'
    VMName = '<vm-name>'
    Name = 'CustomScriptExtension'
    TypeHandlerVersion = '1.10'
    Location = '<azure-region-name-or-code>'
}
Set-AzVMExtension @commandSettings

Распространенные ошибки в тестовом скрипте расширения пользовательского скрипта

  • Ненулевой код выхода. После запуска скрипта Test-CustomScriptExtension.ps1 ожидается следующее сообщение об ошибке:

    Длительная операция завершилась сбоем с состоянием "Сбой".
    Дополнительные сведения. Виртуальная машина сообщила о сбое при обработке расширения CustomScriptExtension.
    Сообщение об ошибке: "Выполнение команды завершено, но не удалось, так как он вернул код выхода, отличный от нуля: "2""

    Тестовый скрипт выполняет Exit 2 команду, и расширение пользовательских скриптов должно завершиться сбоем, если скрипт возвращает код выхода, отличный от нуля. (В этом примере 2 — это ненулевой код выхода.) В этом примере показано, как отображается сбой в дополнительном ведении журнала PowerShell, которое вы включили.

  • Изменение конфликтует: эта ошибка указывает, что на виртуальной машине уже установлено расширение пользовательских скриптов в качестве имени ресурса Microsoft.Compute.CustomScriptExtension, но вы указываете другое имя ресурса CustomScriptExtension для текущего выполнения:

    Невозможно обновить handlerVersion или autoUpgradeMinorVersion для расширения виртуальной машины CustomScriptExtension.
    Изменение конфликтует с другими расширениями в обработчике Microsoft.Compute.CustomScriptExtension, с typeHandler версии "1.10" и autoUpgradeMinorVersion "True".
    Код ошибки: OperationNotAllowed
    ErrorMessage: не удается обновить handlerVersion или autoUpgradeMinorVersion для расширения виртуальной машины CustomScriptExtension.
    Изменение конфликтует с другими расширениями в обработчике Microsoft.Compute.CustomScriptExtension, с typeHandler версии "1.10" и autoUpgradeMinorVersion "True".
    ErrorTarget:
    StatusCode: 409
    ReasonPhrase: Conflict

    В имени ресурса можно указать все, что нужно. Однако если у вас уже установлено расширение пользовательских скриптов, необходимо выполнить одно из следующих действий:

    • Используйте то же имя для последующих выполнений.
    • Сначала удалите этот ресурс расширения, прежде чем использовать другое имя ресурса.
  • Недопустимая конфигурация URI файла. Эта ошибка указывает, что расширение пользовательских скриптов изначально было установлено вместе с URI файлов, которые указаны в защищенных параметрах, но теперь указываются в общедоступных параметрах (или наоборот):

    Длительная операция завершилась сбоем с состоянием "Сбой".
    Дополнительные сведения. Виртуальная машина сообщила о сбое при обработке расширения CustomScriptExtension.
    Сообщение об ошибке: "Недопустимая конфигурация — FileUris присутствует в разделах защищенной и общедоступной конфигурации; он должен быть указан только в одном разделе".

Решения распространенных ошибок в тестовом скрипте расширения пользовательских скриптов

  • Чтобы исправить ошибку "Изменение конфликтует", попробуйте повторно запустить Set-AzVMCustomScriptExtension или Set-AzVMExtension, но задайте -Name для параметра имя ресурса расширения пользовательских скриптов, который уже установлен на виртуальной машине. Для примера ошибки при удалении расширения укажите -Name параметр Microsoft.Compute.CustomScriptExtension. -Name Но параметр должен иметь имя ресурса расширения, который уже установлен на виртуальной машине.

    В этой части ошибки будет использоваться -Name имя ресурса: "Изменение конфликтует с другими расширениями в обработчике resource-name<>".

    Вы также можете проверить правильное имя ресурса для использования, получив его из состояния виртуальной машины. Введите командлет Get-AzVM следующим образом:

    $status = Get-AzVM -ResourceGroupName <resource-group> -Name <vm-name> -Status
    $status.Extensions |
    Where-Object Type -EQ 'Microsoft.Compute.CustomScriptExtension' |
    Select-Object Name
    
  • Чтобы устранить ошибки "Изменение в конфликте" и "FileUris", удалите существующее расширение пользовательских скриптов, введя командлет Remove-AzVMCustomScriptExtension :

    $params = @{
        ResourceGroupName = '<resourceGroupName>'
        VMName = '<vm-name>'
        Name = '<extension-resource-name>'
        Force = $True
    }
    Remove-AzVMCustomScriptExtension @params
    

    Если указать неправильное имя ресурса, возвращается StatusCodeNoContentзначение :

    RequestId IsSuccessStatusCode StatusCode ReasonPhrase
    --------- ------------------- ---------- ------------
                             True  NoContent No Content
    

    Если указать правильное имя ресурса, возвращается StatusCodeOKзначение :

    RequestId IsSuccessStatusCode StatusCode ReasonPhrase
    --------- ------------------- ---------- ------------
                             True         OK OK
    

Ссылки

Свяжитесь с нами для получения помощи

Если у вас есть вопросы или вам нужна помощь, создайте запрос в службу поддержки или обратитесь за поддержкой сообщества Azure. Вы также можете отправить отзыв о продукте в сообщество отзывов Azure.

Заявление об отказе от ответственности за контактные данные сторонней организации

Корпорация Майкрософт предоставляет сторонние контактные данные, чтобы помочь вам найти дополнительные сведения по этой теме. Эти данные могут быть изменены без предварительного уведомления. Корпорация Майкрософт не гарантирует точность контактной информации сторонних поставщиков.