На разных компьютерах периодически встречаю проблему очень долгого запуска консоли PowerShell, вплоть до нескольких минут. Это влияет как на запуск самой командной оболочки powershell.exe/pwsh.exe, так и на время выполнения PowerShell скриптов, которые стартуют через GPO или по расписанию через планировщик задач Windows. В этой статье мы рассмотрим, как обнаружить возможные причины медленного запуска консоли PowerShell и уменьшить время загрузки оболочки.
Типовые причины, из-за которых PowerShell может долго загружаться
- Наличие пользовательских функций в файлах профилей PowerShell, которые загружается при каждом запуске процесса powershell.exe
- Большое количество установленных модулей, которые загружаются автоматически
- Большой файл с историей команд PowerShell, который загружается модулем PSReadLine
- Медленная работа (конфликты/повреждения) компонентов .NET Framework, которая вызывает существенное замедление загрузку PowerShell и модулей
Для измерения времени запуска процесса PowerShell можно использовать командлет Measure-Command. Время загрузки оболочки в обычном режиме можно проверить командой:
powershell -noprofile -ExecutionPolicy Bypass ( Measure-Command { powershell "Write-Host 1" } ).TotalSeconds
Команда выведет время, которое потребовалось на загрузку процесса powershell и выполнения простой команды.
PowerShell при запуске может писать время, затраченное на загрузку.
Loading personal and system profiles took XXXXX ms
В этом примере видно, что процессу PowerShell потребовалось 12 секунд на загрузку. Что довольно много. Такое сообщение автоматически выводится, если время загрузки профилей PowerShell превысило 500 мс.
Это указывает на то, что в файлах профилей PowerShell загружается какой-то медленный код, который каждый раз отрабатывает при старте процесса.
Проверьте, насколько быстрее PowerShell загрузится без профилей. Для этого запустите процесс оболочки с параметром -noprofile:
powershell.exe -noprofile
или для запуска нового PowerShell Core:
pwsh.exe -noProfile
В данном случае видно, что без профилей PowerShell стартовал гораздо быстрее. Профили PowerShell это PS1 файлы, которые используются для настройки окружения пользователи, выполнения определенных команд и загрузки кастомных функций/модулей (подробнее про файлы профилей PowerShell).
По умолчанию в Windows файлы профилей PowerShell не используются (файлы не созданы). Список всех файлов профилей, которые будут загружаться при запуске процесса powershell.exe можно вывести так:
$profile | select *
Проверьте содержимое всех файлов профилей для пользователя вручную, или выведите его с помощью команд:
Get-Content $PROFILE.AllUsersAllHosts
Get-Content $PROFILE.AllUsersCurrentHost
Get-Content $PROFILE.CurrentUserAllHosts
Get-Content $PROFILE.CurrentUserCurrentHost
В моем случае в пользовательском файле Microsoft.PowerShell_profile.ps1 выполнялись некоторые команды. Проверьте, все ли команды/функции в файле профиля вам нужны, удалите неиспользуемые, оптимизируйте код если возможно.
Другой возможной причиной медленного запуска оболочки PowerShell может быть большое количество установленных модулей. PowerShell автоматически загружает модули, скопированные в следующие папки:
C:\Users\%username%\Documents\WindowsPowerShell\Modules C:\Program Files\WindowsPowerShell\Modules C:\Windows\system32\WindowsPowerShell\v1.0\Modules
Их список можно вывести так:
$Env:PSModulePath -split ';'
Вывести список загруженных в сессию модулей можно командой:
Get-Module -ListAvailable
Вывести список установленных сторонних модулей:
Get-InstalledModule
Проверьте, все ли модули из списка вам нужны. Удалите неиспользуемые модули:
Remove-Module -Name BurntToast
Uninstall-Module -Name BurntToast -AllVersions -Force
Для анализа времени загрузки каждого модуля можно использовать команду:
Measure-Command { Import-Module ModuleName -Force }
Либо массово проверяем время запуска всех модуля:
$modules = Get-InstalledModule| Select-Object -ExpandProperty Name -Unique
foreach ($mod in $modules) {
$time = Measure-Command { Import-Module $mod -Force }
[PSCustomObject]@{
Module = $mod
LoadTimeSec = $time.Totalseconds
}
}
Посмотрите, какие модули загружаются дольше всего.
Чтобы полностью запретить автоматическую загрузку модулей, нужно добавить в файл профиля строку:
$PSModuleAutoLoadingPreference = 'None'
В этом случае, чтобы загрузить нужный вам модуль вручную нужно будет выполнить команду:
Import-Module [module-name]
Import-Module Microsoft.PowerShell.Utility
Import-Module Microsoft.PowerShell.Management
Также есть известная проблема с загрузкой PowerShell модуля для управления инфраструктурой VMware (VMware PowerCLI). Время загрузки модуля на компьютере, на котором отсутствует подключение к Интернету, может занять несколько минут. Проблема в том, что при загрузке модуль пытается проверить внешний список отозванных сертификатов (CRL, Certificate Revocation List). Из-за отсутствия доступа в Интернет этот процесс отваливается по таймауту. Решение – отключить проверку списка отозванных сертификатов. Можно отключить проверку CRL через реестр:
reg add HKLM\SYSTEM\CurrentControlSet\Services\SstpSvc\Parameters /v NoCertRevocationCheck /t REG_DWORD /d 0x00000001 /f
Или в свойства проводника:
inetcpl.cpl
-> Advanced –> Check for publisher (server) certificate revocation.
Часто причиной медленного запуска PowerShell может быть установленный на компьютере антивирус. Можно проверить, какие внешние dll библиотеки и модули загружаются при старте процесса PowerShell.
- Откройте
cmd.exe
и выполните команду:powershell.exe -c "Write-Host $PID;Start-Sleep -s 30"
- Команда вернет PID запущенного процесса powershell.exe. Скопируйте его и вставьте в следующую команду в новой сессии PoSh:
Get-Process | where {$_.Id -eq <YOUR_PID>} | select -ExpandProperty modules
- Вы получите список DLL, которые загружаются при запуске процесса powershell.exe. Проверьте, есть ли в списке библиотеки вашего антивируса. Если есть, попробуйте добавить процесс pwsh.exe и powershell.exe в исключения вашего антивируса.
Например, в антивирус Windows Defender можно добавить исключения с помощью PowerShell:
Add-MpPreference -ExclusionProcess "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe","C:\Program Files\PowerShell\7\pwsh.exe"
Если вы подозреваете, что причиной медленного запуска powershell.exe являются медленная работа библиотек .NET Framework, можно скомпилировать все используемые сборки (assemblies) в нативный машинный код с помощью утилиты ngen.exe (Native Image Generator). Это существенно увеличит производительность приложений .NET, включая PowerShell:
$env:PATH = [Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory()
[AppDomain]::CurrentDomain.GetAssemblies() | ForEach-Object {
$path = $_.Location
if ($path) {
$name = Split-Path $path -Leaf
Write-Host -ForegroundColor Yellow "`r`nRunning ngen.exe on '$name'"
ngen.exe install $path /nologo
}
}
Также для анализа времени запуска PowerShell можно использовать классическую утилиту Process Monitor (https://technet.microsoft.com/en-us/sysinternals/processmonitor.aspx). Запустите
procmon64.exe
, включите фильтр по процессу powershell.exe, запустите консоль и найдите операции, которые вызвали наибольшую задержку.
В моем примере чтение файла с историей PowerShell команд, который ведет модуль PSReadline заняло около 30 секунд (
C:\Users\sysops\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ ConsoleHost_history.txt
). Как оказалось проблема была вызвана большим размером файла ConsoleHost_history.txt (около 2 Гб). После его очистки, время запуска powershell заметно сократилось.