Windows 11, 10, etc - Вадим Стеркин
14.7K subscribers
281 photos
6 videos
8 files
1.1K links
Авторский канал. Windows, безопасность, мобильный мир:
• тайное знание
• мощный ликбез
• гадание по логам
• срыв покровов
• доставка пруфов

Чат: @winsiders
Блог: outsidethebox.ms
ЛС: @vsterkin
Донаты ₽: boosty.to/sterkin
РКН: https://clck.ru/3LBugC
Download Telegram
⚙️ Как быстро восстановить разрешения на файл или папку

Недавно в чат поддержки пришел человек, который собственноручно сломал разрешения файла hosts. Он творил полную дичь - пытался сделать этот файл только для чтения, чтобы не слетел некий кряк 🤦‍♂️

Зато у меня появился повод показать один из моих любимых примеров мощи конвейеров в #PowerShell. От имени администратора:

cd C:\Windows\System32\drivers\etc
Get-Acl networks | Set-Acl hosts


В оригинале была одна команда, но так нагляднее. Перейдя в папку с файлами, берем список контроля доступа (ACL) у соседнего файла networks и применяем его к поврежденному hosts. Это всё! Восстанавливаются разрешения на объект файловой системы и его владелец.

ℹ️ Параметр -Path для пути к файлу я опустил. У этих командлетов он первый позиционный, т.е. подразумевается, если не указан. Такие вещи описаны в справке.

Проблему автора вопроса это решило, но ничему не научило. Уже через 5 минут он спросил, как изменить владельца родительской папки etc. Потому что не получалось сохранить измененный файл hosts 🤦‍♂️🤦‍♂️ В его инструкциях же не уточнялось, что текстовый редактор надо запускать от админа...

Вообще, правка разрешений нужна крайне редко. А для внесения изменений в файлы или реестр она фактически не требуется. На такой случай у меня есть отличная #классика блога с быстрым и грамотным способом выполнения операций с правами SYSTEM и TrustedInstaller ✌️
🤷‍♂️ Владельцы локализованных Windows должны страдать

Особенно те, кто устанавливают систему с локализованного дистрибутива, нежели накатывает языковой пакет поверх оригинальной английской системы. Ну, может, и не должны, но регулярно страдают :)

Этот тезис получил очередное подтверждение на прошлой неделе. Множество пользователей VMWare Workstation из разных стран не смогли установить новую версию. Всех их объединяло наличие локализованной системы.

An error occurred while applying security settings. Authenticated Users is not a valid user or group.

Методом тыка страдальцы выяснили: установка проходит на ура, если создать в системе две группы с латинскими названиями - Authenticated Users и Users. Причем членство в этих группах не требуется.

Видимо, установщик просто проверял наличие этих групп по именам. Но такие имена будут только при установке с английского дистрибутива! А вот дальнейшая установка языкового пакета на имена групп уже не повлияет 👌

///

По совпадению я недавно разбирал похожую ситуацию на работе. Нас мигрируют в огороженную среду с правами обычного пользователя, поэтому повылезало много проблем с установкой бизнес-приложений. Детали опущу, но вкратце - некоторые программы легко ставятся мимо Program Files и нормально работают дальше. Прочие же этому противятся, причем нетрадиционными способами 🌈

Нормальные разработчики уже лет дцать используют манифест установщика. И если там указано requireAdministrator, он будет запускаться только с правами администратора. А тут некое приложение с бородатых времен поставляется в самораспаковывающемся CAB-архиве. Я извлек содержимое командой expand и увидел несколько батников.

Первый батник сходу проверял наличие прав администратора, а при успехе вызывал другие файлы, устанавливающие службу и прочие штуки. Для проверки он искал в выводе команды whoami /groups строку Mandatory Label\High Mandatory Level 🤦‍♂️

Сравните самостоятельно результат выполнения команды в консоли с правами обычного пользователя и администратора. Искомая строка будет только во втором случае, потому что процесс имеет высокий уровень целостности. У классических приложений обычного пользователя он средний (Medium).

🪲 Казалось бы, цель достигнута. Однако вывод команды локализован! Поэтому в русской или итальянской Windows вы с любыми правами такого не увидите. И соответственно вообще не сможете установить приложение этим инсталлятором.

Я предложил инженеру QA занести на это дефект, но он махнул рукой. Видимо, клиентская база использует исключительно английские ОС. Так-то даже на уровне батников можно было решить вне зависимости от языка. Например, такой костыль #PowerShell вызовет из одного батника другой с правами админа.

powershell -command "Start-Process -FilePath '%~dp0batch2.cmd' -Verb RunAs"

Глагол RunAs недавно был в канале. Я также показывал управление локальными пользователями и группами вне зависимости от языка ОС.

////

Я исторически использую Windows на английском языке, поэтому подобные проблемы мне не грозят. Если для вас такое невыносимо, хотя бы ставьте английский дистрибутив и накатывайте родной языковой пакет. Иначе - не забывайте так страдать ✌️
🆕 Что нового в Windows 11 24H2 для ИТ-специалистов и разработчиков

Для начала всем интересующимся Windows рекомендую большие подборки Community (RU) и Winaero (EN).

ℹ️ Для ИТ-специалистов у Microsoft есть статья What's new in Windows 11, version 24H2 с ценным упоминанием о новой иконке диспетчера задач и эффекте Mica в его настройках :) Отмечу многочисленные изменения в SMB и LAPS, а также новинку - Personal Data Encryption (PDE). Это корпоративное шифрование пользовательских папок на уровне файлов под крылом Windows Hello и под управлением Intune.

Для разработчиков я нашел изменения в:
API электропитания в той же статье для IТ Pro
доступе к Wi-Fi и местоположению
разработке драйверов

В остальном Microsoft делает упор на ИИ - NLP, OCR, живые субтитры, Recall.

////

Помимо списка ссылок расскажу про два заинтересовавших меня нововведения в 24H2.

🔁 Чекпойнты накопительных обновлений
Первый выпуск Windows 11 привнес ряд заметных улучшений в накопительные обновления. В 24H2 процесс эволюционировал. До сих пор при доставке обновлений дельта вычислялась относительно состояния компонентов в [условном] RTM.

Теперь некоторые накопительные обновления служат контрольными точками - чекпойнтами, и дельта вычисляется уже относительно них. Тем самым уменьшается размер ежемесячных обновлений и экономится место на диске, а установка проходит быстрее.

Сокращение размера обновлений будет наиболее заметным в сценарии, когда новая версия ОС доставляется постепенно в рамках накопительных обновлений и включается тумблером. Так было с Windows 11 23H2 и Windows 10 многократно. Теперь #тумблер будут выпускать наряду с чекпойнтом. Он и станет базой для следующих накопительных обновлений, нежели прошлогодний RTM.

Подробнее в блоге IT Pro 📃

▶️ Sudo для Windows
Тут пошли по стопам Linux. Если выполнить с обычными правами, например: fsutil fsinfo ntfsinfo C:, будет ошибка из-за недостатка прав. Придется перезапускать консоль с полными правами.

Включив sudo в настройках разработчика в Параметрах, можно в той же консоли добавить sudo к команде: sudo fsutil fsinfo ntfsinfo C:, одобрить запрос UAC и получить результат!

Подробнее о параметрах sudo для Windows в документации 📃

Запрос UAC придется одобрять каждый раз. В этом заметное отличие UX от Linux, где после ввода пароля его не просят для sudo на протяжении N минут (настраивается) 🐧

Кэш учетных данных есть в gsudo, которая также может повышать команды #PowerShell. Нативная утилита sudo этого не умеет, но у меня есть #классика блога: sudo и resudo в PowerShell ✌️
🔎 Как очистить историю поисковых запросов в меню Пуск

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

Казалось бы, простой вопрос, но ответ не лежит на поверхности.

ℹ️ Для начала поиск в Пуске - это фактически отдельное приложение Поиск (Win+S). Именно в него вы переключаетесь, когда в Пуске ищете приложения или настройки либо выполняете поиск в интернете. Все недавние запросы видны сразу при открытии поиска любым способом.

В статье базы знаний KB4553482 есть раздел "Журнал поиска на этом устройстве" с инструкциями по очистке из Параметров. Также в интернетах можно найти совет сбросить приложение Client.CBS (Feature Experience Pack) из #PowerShell:

Get-AppPackage -Name MicrosoftWindows.Client.CBS | Reset-AppxPackage

Якобы все это удаляет историю поисковых запросов в меню Пуск. И это даже правда. Но есть нюанс © Эти действия очищают историю запросов приложения Поиск, хранящуюся локально 💻

Как следствие, это еще не конец нашей истории, а только середина!

☁️ В приложение Поиск возможен вход с учетной записью Microsoft (MSA). Из коробки это происходит автоматически при:
• использовании MSA в Windows вместо локального аккаунта
• первом входе с MSA в любое приложение Microsoft, что влечет за собой вход с этим аккаунтом во все приложения компании

👤 Визуально связь с облачным аккаунтом определяется по аватарке в правом верхнем углу поиска. И в этом случае все поиски в интернете сохраняются в облаке. Более того, там собраны все запросы в Bing, где бы вы их ни делали.

👉 При использовании MSA очистка локальной истории и сброс Client.CBS не помогут убрать из списка поиски в интернете. Потому что вся история Bing заново подтянется в локальное хранилище из облачного аккаунта! Историю поиска в интернете надо удалять в настройках конфиденциальности учетной записи Microsoft, а именно - в истории поиска!

Если внимательно присмотреться к истории запросов dartraiden, там явно не взрослый мужик, а школьники что-то ищут в интернете 😎 Как выяснилось, это "аккаунт, купленный за копейки для доступа к геймпассу". То есть им пользуется множество людей. Поэтому история будет активно пополняться чужими запросами в любом случае!

Кардинальное решение - локально отключить все обращения к Bing политикой или твиком реестра, тут #классика блога в помощь! Это полностью разрывает связь с облаком - поиск перестает подкачивать историю запросов в Bing и веб-содержимое, обретая аскетичный вид. А поиски приложений и настроек можно очистить из Параметров.

The End ✌️
⚙️ Приложения в образе Windows 11 24H2 - состав и удаление

С выходом каждой новой версии Windows я обновляю свой скрипт #PowerShell для удаления ненужных приложений из образа. Он вполне безвредный, поскольку не затрагивает магазин и его служебные приложения, центр безопасности, а также расширения для воспроизведения медиа.

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

Get-AppxProvisionedPackage -Online | select DisplayName

Каждый год я сравниваю старый список с новым и смотрю, что изменилось. В этом году заметная смена состава!

Из служебных пакетов появились три расширения для медиа: AV1, AVCEncoder, MPEG2. А пакет Microsoft.VCLibs.140.00 больше не входит в образ. Дальше - приложения!

Выбыли

🤷‍♂️ 549981C3F5F10 - Cortana, и я затрудняюсь сказать, какую функцию пакет выполнял в последнее время

ℹ️ Getstarted - никому не нужны эти ваши ролики для знакомства с Windows :)

⚰️ People - людей убили достаточно давно, теперь похоронили

📩 windowscommunicationsapps - старые почта и календарь

🗺 WindowsMaps - внезапно, но приложение есть в магазине

🎮 XboxGameOverlay - что-то про GameBar, причем остался XboxGamingOverlay

⏯️ ZuneVideo - Кино и ТВ, и теперь по умолчанию все медиафайлы воспроизводит современный медиаплеер, aka ZuneMusic

Добавились

🔎 BingSearch - затруднюсь объяснить назначение, т.к. поиск в Пуск и панели задач работает и без него

👨‍👩‍👧‍👦 MicrosoftFamily - семейная безопасность (только в Home)

🤞 CrossDevice - это вдобавок к YourPhone, и, возможно, на этот пакет завязаны беспроводное управление файлами телефона и грядущая фича Resume

💬 MSTeams - ранее Teams доставляли после первого входа в систему, и приходилось предотвращать установку

📨 OutlookForWindows - новые почта и календарь, унылые

🧑‍💻 DevHome - в 23H2 прилеталo после первого входа, как и Outlook, причем предотвращать их установку было сложно

////

По традиции я не удаляю выпиленные пакеты из скрипта, а перемещаю их в конец - в качестве неактуальных. Я исследую только последнюю версию Windows 11, а кто-то может готовить образ Windows 10.

На картинке пакеты, которые остаются после работы скрипта. Разумеется, вы можете закомментировать любое приложение и тем самым сохранить его в образе. Равно как всё удаленное можно установить из магазина при необходимости ✌️
⚙️ О зачистке Windows от хвостов приложений

У меня на нескольких системах было установлено приложение VMWare Horizon для удаленного доступа к ВМ заказчика. Тот перешел на Windows 365, поэтому я штатно удалил клиент VMWare из всех ОС и забыл про него.

А спустя несколько месяцев случайно увидел запущенные службы VMWare. На одном компьютере их было три, а на другом аж пять 🤦‍♂️ Что ж, надо зачистить!

Я исторически не доверяю приложениям типа Revo Uninstaller Они в принципе не могут знать о программе больше, чем ее штатный установщик. А выполняя удаление по косвенным признакам, они могут зацепить что-то важное, о чем я знать не буду.

👉 В таких ситуациях я предпочитаю действовать руками. И первой отправной точкой всегда должен быть раздел реестра:

HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall

Я не помню путь наизусть, поэтому всегда гуглю uninstall windows registry и попадаю в KB247501. В этом разделе содержатся сведения обо всех установленных программах. Отсюда подтягивают данные классический апплет appwiz.cpl и раздел Параметров с установленными приложениями.

У большинства подразделов в качестве имени используется GUID, но принадлежность к программе несложно определить визуально. Также можно сделать выборку скриптом #PowerShell за авторством ChatGPT.

Важны эти параметры:
DisplayName - название программы
UninstallString - команда для удаления

У VMWare Horizon в качестве инсталлятора используется Windows Installer. Поэтому для удаления вызывается msiexec с ключом /x, которому передается ИД программы в базе установщика Windows:

MsiExec.exe /X{2B1D0F22-6025-409A-A248-7C10783FD5F2}

После выполнения всех команд из системы удалились службы и оставшиеся папки приложения Horizon✌️
🕒 Как получить сведения о запланированном задании в PowerShell

Смотрел тут свои старые скрипты, решил поделиться. Задача несложная, однако в зависимости от цели может понадобиться три разных запроса.

1️⃣ Состояние задания - Get-ScheduledTask

Годится запрос по имени задания, в том числе неполному. Заодно выведем чуть больше инфо - полное имя, состояние, адрес.

Get-ScheduledTask -TaskName '*sync*lang*' | fl TaskName,State,URI

TaskName : Synchronize Language Settings
State : Ready
URI : \Microsoft\Windows\International\Synchronize Language Settings


Задание синхронизации языковых настроек включено.

2️⃣ Сведения о последним запуске задания - Get-ScheduledTaskInfo

Здесь наряду с именем задания требуется еще и путь в -TaskPath. Либо в качестве имени указывайте URI из запроса выше.

Get-ScheduledTaskInfo -TaskName '\Microsoft\Windows\International\Synchronize Language Settings

LastRunTime : 1/25/2025 10:22:22
LastTaskResult : 0
NextRunTime :
NumberOfMissedRuns : 0
TaskName : \Microsoft\Windows\International\Synchronize Language Settings


LastTaskResult выводит код завершения предыдущего запуска. 0 означает успех, 1 - неудачу, остальное надо смотреть по коду.

На практике задачи 1 и 2 чаще объединяются в конвейер. Причем можно сходу фильтровать по неполным именам нескольких заданий, обходясь без Where-Object.

Get-ScheduledTask -TaskName *backup*,*sync* | Get-ScheduledTaskInfo | fl TaskName,LastRunTime


3️⃣ Параметры задания - Export-ScheduledTask

Export-ScheduledTask -TaskName '\Microsoft\Windows\International\Synchronize Language Settings'


Выводится XML, как в экспорте задания из планировщика. PowerShell легко перемещается по узлам.

[xml]$task = Export-ScheduledTask -TaskName '\Microsoft\Windows\International\Synchronize Language Settings'
$task.Task.Triggers.LogonTrigger.Delay

PT30S


Задание отложено на 30 секунд после входа в систему.

////

Создание запланированного задания в #PowerShell будет посложнее. Я показывал это в блоге, в том числе в сравнении с утилитой schtasks. Бывает проще импортировать подготовленный XML. Пример уже был на канале ✌️
🧹 Удаление временных файлов из служебных профилей

Недавно я анализировал недостаток дискового пространства на одной системе. Помимо прочего утилита dfp выдала такое:

1.6G      527      191 C:\Windows\ServiceProfiles
1.6G 287 81 C:\Windows\ServiceProfiles\NetworkService
1.6G 281 71 C:\Windows\ServiceProfiles\NetworkService\AppData
1.6G 244 39 C:\Windows\ServiceProfiles\NetworkService\AppData\Local
1.6G 243 34 C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Temp


Я натравил её на папку Temp и выяснил, что 1.6 GB занято древней папкой с именем типа E58DAD6F-63DE-461C-AB9F-7F58DDC916D9

ℹ️ NetworkService и LocalService - служебные аккаунты, чьи профили хранятся в папке Windows. Их содержимое аналогично профилям интерактивных пользователей из папки C:\Users.

Удаление временных файлов безвредно, причем ухищрений не требуется. В смысле не надо ломиться в системные папки как лось, меняя на ходу разрешения NTFS. Просто в #PowerShell от имени администратора:

Remove-Item -Force -Recurse -Path 'C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Temp\E58D*'


Total Commander или FАR от админа тоже годятся ✌️
⚙️ Нюансы префетчинга приложений в Windows

Сегодня в рубрике "Возвращаясь к напечатанному" префетчинг приложений и количество записей, которые он хранит и обрабатывает.

• В 2013 году я разбирал префетчинг в рамках мифов оптимизации SSD
• В 2019 году он снова попал в фокус в статье Нюансы отключения службы SysMain в Windows

Niks спросил, работает ли префетчинг по умолчанию, когда система установлена на SSD. Да, работает, и это легко проверить по мотивам статьи про SysMain:

Get-MMagent

ApplicationLaunchPrefetching : True
ApplicationPreLaunch : True
MaxOperationAPIFiles : 512
MemoryCompression : True
OperationAPI : True
PageCombining : True


Параметр ApplicationLaunchPrefetching - это он. Вообще, этот префетчинг еще времён XP, и по большому счету с тех пор ничего не изменилось. Однако сравнивая вывод наших команд, я заметил, что отличается значение MaxOperationAPIFiles. Это предельное количество файлов в сфере префетчинга.

🔢 В Windows 10 и ранее оно по умолчанию 256, а в Windows 11 — 512 (так же и в серверных ОС). Думаю изменение связано с тем, что сама ОС разрослась с 2006 года и запускает больше своих исполняемых файлов.

Niks интересовался, стоит ли увеличить значение до 2048. Чтобы ответить на этот вопрос, нужно:

1. Понимать, запускаются ли .EXE с жесткого диска
2. Проанализировать файлы в папке C:\Windows\Prefetch\

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

(Get-ChildItem "C:\Windows\Prefetch\*.pf" -File).Count

ℹ️ На количество файлов могут влиять разные факторы. Например, едва установив систему, вы начинаете ставить и запускать любимые приложения. Каждый установщик - это как минимум один исполняемый файл. С другой стороны, после чистой установки система сама оптимизирует себя, что может быть сопряжено с запуском различных .EXE.

Все это оседает в папке Prefetch. По достижении предела старые записи будут вытеснены автоматически, но "одноразовые" установщики и не нужны.

Если в обжитой системе вы запускаете .EXE с HDD, а количество PF-файлов близко к лимиту, может иметь смысл увеличить предельное значение.

Set-MMAgent -MaxOperationAPIFiles 1024

Так, у Niks спустя две недели после очистки и увеличения лимита в папке стало 354 PF-файла. Что превышает стандартный предел в его Windows 10, но далеко от 512 в Windows 11.

Измерить же реальный прирост скорости запуска исполняемых файлов затруднительно. Зато душу будет греть тот факт, что вы оптимизировали свою ОС не случайно найденной в интернетах подборкой твиков, а на основе грамотного анализа ✌️
🚀 Ускорение загрузки файлов в PowerShell

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

1️⃣ Отключение прогресса
$ProgressPreference = 'SilentlyContinue'

В принципе, эта переменная может слегка ускорить процесс и снизить нагрузку на ЦП не только при скачивании файлов. Но конкретно для командлета Invoke-WebRequest (псевдоним iwr) разница значительная! Один и тот же файл размером 175 мегабайт без прогресса у меня на разных системах скачивался от 16 до 42 секунд, а с прогрессом - от 3 до 13 минут! 🤔

У этого командлета проблема наблюдается только в Windows PowerShell 5.1, но не в PowerShell Core. Я писал об этом в канале 7 лет назад :) И недавно краем глаза где-то видел, что якобы в 5.1 уже починили. Мои тесты это не подтверждают.

2️⃣ Подстановка юзер-агента
На самом деле даже без прогресса у меня Invoke-WebRequest качал слишком долго. Я предположил, что сайт специально притормаживает отдачу, и решил прикинуться браузером.

-UserAgent "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"

После этого скорость окончательно наладилась!

3️⃣ Загрузка с помощью BITS
В Windows помимо командлетов Invoke-WebRequest и Invoke-RestMethod также можно скачивать файлы с помощью Start-BitsTransfer. Этот командлет запускает джоб с помощью службы BITS (фоновая интеллектуальная служба передачи данных). Именно ее использует центр обновления Windows.

Это не совсем про ускорение, а про альтернативы. Но в моем случае был сайт Microsoft, и родную BITS он не тормозил в отличие от iwr без юзер-агента.

////

▶️ Скрипт для экспериментов:

$file = "$env:temp\mpam-fe.exe"
$uri = 'https://go.microsoft.com/fwlink/?LinkID=121721&arch=x64'
$u = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
$ProgressPreference = 'SilentlyContinue'

Measure-Command {
Invoke-WebRequest -Uri $uri -OutFile $file -UserAgent $u
}
Measure-Command {
Start-BitsTransfer -Source $uri -Destination $file
}


Скрипт скачивает актуальный файл сигнатур защитника Windows с этой страницы. Но учтите, что для обновления сигнатур из консоли есть методы получше ✌️
🔍 Расследование: кто перезагрузил Windows

В чате участник A S. сообщил, что в Windows 11 политика NoAutoRebootWithLoggedOnUsers не предотвращает перезагрузку после установки обновлений. Статью про это я написал в 2018 году и с тех пор не перепроверял. Причем 4 года спустя эту политику наряду с другими объявили устаревшей и не поддерживаемой в Windows 11. И пообещали в будущем выпилить.

У ТС все подробности свелись к "отхожу на несколько часов". Обычно, так быстро сразу после установки обновлений перезагрузка не происходит. Стандартно - в 3 часа ночи, но дальше может в любой момент вне активных часов 🕒

Проще проверить всё самому, и я зарядил виртуалку. За несколько часов ничего не произошло, как и с утра. Однако ночью я сплю, и моя основная ОС тоже, т.е. ВМ в ней неактивны. Поэтому наутро я изменил часовой пояс ВМ и продолжил тест.

👉 Перезагрузка произошла! Но чтобы обвинить в ней обновление Windows, надо ответить на второй по важности вопрос всех времен и народов: какие ваши доказательства?

Они собраны в журнале событий, поэтому пост публикуется в рубрике "Гадание по логам" с помощью #PowerShell.

1️⃣ Смотрим время загрузки ОС - сразу после 3 часов ночи. Уже горячо!

(Get-CimInstance Win32_OperatingSystem).LastBootUpTime

Sunday, February 2, 2025 3:01:46 AM


2️⃣ Выясняем, какие процессы это вызвали - событие 1074 в журнале Система:

Get-WinEvent -FilterHashtable @{LogName='System'; Id=1074} | 
Select-Object TimeCreated, Message | Format-List

TimeCreated : 2/2/2025 3:00:03 AM
Message :
The process C:\WINDOWS\uus\AMD64\MoUsoCoreWorker.exe (DESKTOP-GQKOJ6B)
has initiated the restart of computer DESKTOP-GQKOJ6B on behalf of
user NT AUTHORITY\SYSTEM for the following reason:
Operating System: Service pack (Planned)
Reason Code: 0x80020010
Shutdown Type: restart.


3️⃣ Проверяем связь с планировщиком - поиском по части имени файла MoUsoCoreWorker. Время совпадает!

Get-ScheduledTask | where TaskName -match 'Uso' | Get-ScheduledTaskInfo

LastRunTime : 2/2/2025 3:00:05 AM
LastTaskResult : 2147942402
NextRunTime : 2/3/2025 10:48:35 PM
NumberOfMissedRuns : 0
TaskName : USO_UxBroker
TaskPath : \Microsoft\Windows\UpdateOrchestrator\


4️⃣ Но это еще не всё! Надо определить, был ли в это время выполнен вход пользователя в систему. Ведь политика должна предотвращать перезагрузку только в этом случае.

Здесь фильтруем события входа и выхода, затем ищем нужные по части SID - 1001 в конце у администраторов.

Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624,4634,4647} | 
where message -match '1001' | Select-Object Id, TimeCreated, Message -First 15 |
ft -AutoSize

Id TimeCreated Message
-- ----------- -------
4634 2/2/2025 3:20:10 AM An account was logged off....
4624 2/2/2025 3:03:05 AM An account was successfully logged on....
4647 2/2/2025 3:00:05 AM User initiated logoff:...
4624 2/1/2025 11:33:32 PM An account was successfully logged on....

Событие 4647! Выход четко совпадает по времени с выполнением запланированного задания оркестратора. Указано, что пользователь инициировал выход, но это издержки регистрации в журналах. По факту он стал неактивен, не отреагировал вовремя на уведомление о перезагрузке, и его выход форсировали.

////

☑️ Итак, мы однозначно определили, что перезагрузку выполнил оркестратор обновлений, проигнорировав групповую политику и завершив пользовательский сеанс.

В Windows 10 политика NoAutoRebootWithLoggedOnUsers продолжает выполнять свою функцию. А в Windows 11 уже нет поддерживаемого способа предотвратить форсированный перезапуск после установки обновлений. Единственный обходной путь - ручное планирование перезагрузки, что позволяет отложить ее максимум на неделю ✌️

P.S. Неподдерживаемый обходной путь здесь.
▶️ Простой и эффективный поиск текста в PowerShell - Select-String

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

Как правило, люди открывают Notepad++, загружают туда пачку логов и выполняют поиск во всех открытых файлах. На предложение использовать скрипт пожимают плечами - мол, это же разовая задача. И так сойдет 👌

У этого подхода много проблем:
1. Медленно.
2. Не выгружается сводка и найденные данные.
3. При большом количестве / размере файлов это работает плохо или вообще никак.

Да и не разовая это у них задача, поэтому скрипт подходит отлично.

👉 В #PowerShell для этой цели есть командлет Select-String. Он умеет искать простое совпадение или регулярное выражение.
Select-String -Path 'C:\logs\*.log*' -Pattern '0x800f0805' -SimpleMatch
Select-String -Path 'C:\logs\*.log*' -Pattern '0x800f0805|0x80070490'

Срыва покровов здесь нет, но есть нюанс. Его я замечал раньше в рамках гугления, а сейчас вижу в скриптах чат-ботов и джунов, что примерно одно и то же :)

🤖 Чат-боту задача по поиску текста ставится общими словами. Например, найди все строки с 'exception ' в этих файлах, посчитай количество на каждый файл, запиши в файл эту строку и 20 следующих, выведи на экран сводку и т.д.

И бот быстро пишет рабочий скрипт. Только он зачастую использует другой командлет - Get-Content и передает весь массив на поиск командлету Where-Object. А для такой задачи это неэффективно - слишком медленно!

Простое сравнение. Ищем ошибку '0x800f0805' десяти файлах cbs.log общим объемом около 1.2GB.

$logpath = "C:\temp\logs\*cbs*.log"
Measure-Command {
$a = Select-String -Path $logpath -Pattern '0x800f0805' -SimpleMatch
}
$a.count
Measure-Command {
$b = Get-Content -Path $logpath | where $_ -match '0x800f0805'
}
$b.count

У меня Select-String справился за 7.5 секунд, а Get-Content | where понадобилось 4 мин и 40 секунд.

💡 И кстати по поводу контекста, у Select-String есть очень полезный параметр -Context, который выводит строки до и после найденного совпадения.

Select-String -Path $logpath -Pattern 'exception ' -SimpleMatch -Context 2,20


////

В общем, если вам нужно дальше работать с чистыми найденными строками в качестве объектов, Get-Content имеет смысл. Но просто для поиска текста Select-String вполне достаточно. И да, можно сказать чат-боту, чтобы применял конкретный командлет!

🍪 Бонус - #классика канала про мониторинг логов в реальном времени при помощи:
• командлета PowerShell
• утилиты CMTrace
🔓 О папке C:\inetpub

Обычно она создаётся при включении необязательных компонентов IIS (встроенные в Windows веб-сервер и FTP-сервер). Однако после апрельского накопительного обновления внимательные люди обнаружили папку в клиентских и серверных Windows даже при выключенных компонентах IIS.

Спустя пару дней популярные сетевые издания сообщили со слов Microsoft, что так и задумано, а удалять папку не следует вне зависимости от наличия включённого IIS. И действительно, 10 апреля в статью с описанием уязвимости CVE-2025-21204: Windows Process Activation Elevation of Privilege Vulnerability безопасники Microsoft добавили специальное примечание:

After installing the updates listed in the Security Updates table for your operating system, a new %systemdrive%\inetpub folder will be created on your device. This folder should not be deleted regardless of whether Internet Information Services (IIS) is active on the target device. This behavior is part of changes that increase protection and does not require any action from IT admins and end users.

ℹ️ Дело тут серьёзное. В соответствии со статьёй, используя изъян в стеке Windows Update, злоумышленник с локальным доступом может легко повысить свои привилегии с обычных до системных. Это называется Elevation of Privilege, EoP.

Слабость классифицируется как CWE-59: Improper Link Resolution Before File Access ('Link Following'). И это похоже на очередную уязвимость в обработке ярлыков Windows, т.е. файлов .LNK. Среди наиболее заметных уязвимостей LNK с EoP из прошлого: CVE-2010-2568 и CVE-2017-8464. Но они были куда опаснее тем, что позволяли удалённое повышение привилегий.

🤔 Казалось бы, предотвращение уязвимости путём создания папки выглядит несерьёзно. Но с одной стороны, Microsoft заявляет это лишь как одну из мер. А с другой - это может быть достаточным временным (надеюсь 🙂) решением до реализации более существенного исправления.

⚙️ Вероятно, уязвимость эксплуатируется путём создания файлов .LNK в C:\inetpub. После установки обновления (и при включении IIS, если на то пошло) для записи в папку и её удаления требуются права системы, TrustedInstaller или администратора.

То есть теперь злоумышленник с ограниченными правами не может ни создать в ней файлы для эксплуатации уязвимости, ни заменить её на свою папку. См. картинку.

////

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

🔹 Удалите апрельское накопительное обновление и установите его заново

🔹 Включите любой компонент IIS (это создаст папку) - клонируйте ACL во временную папку - выключите IIS и перезагрузитесь - создайте папку вручную - клонируйте ACL из временной папки. Разрешения будут не один к одному, но обычного пользователя ограничат.

Ещё можно попробовать дождаться следующего накопительного обновления. Однако я могу лишь предположить, что оно восстановит папку, но не гарантировать. Да и лучше не сидеть месяц с уязвимостью ✌️

P.S. Спустя полтора месяца, Microsoft выпустила скрипт #PowerShell для создания папки с нужными разрешениями NTFS: Set-InetpubFolderAcl.
▶️ Как быстро искать по различным параметрам служб Windows
И не только служб. И не только в Windows!

В чате Николай посетовал :

Когда они сделают сквозной поиск по службам Windows. Ну то есть по всем атрибутам служб. Я вот не могу запомнить как какая служба называется в русскоязычном интерфейсе. Вывели хотя бы в оснастке столбец "Отображаемое имя", но нет.

В диспетчере задач Windows 11 есть приличный сквозной поиск по службам. Но отображаемого имени там нет. И многих прочих параметров тоже.

👉 Николаю сразу посоветовали #PowerShell - командлет Get-Service для получения сведений о службах. Однако ТС хотел графический интерфейс и отрицал этот способ. Тогда я добавил еще один командлет - Out-GridView:

Get-Service | Out-GridView


Это самый настоящий графический интерфейс! Здесь есть:
🔹 сквозной фильтр по всем параметрам
🔹 тонкие критерии фильтров
🔹 сортировка по столбцам

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

Get-Service | Select-Object * | Out-GridView


Дальше вы можете управлять набором столбцов в графическом интерфейсе. И да, их можно сразу перечислить в select.

🏷 Наконец, можно сделать ярлык на такую "оснастку".

////

Этот прием можно применять и к другим оснасткам. Именно так я ковыряюсь в дебрях планировщика Windows. Потому что динамическая фильтрация рулит!

Get-ScheduledTask | Select-Object * | Out-GridView


Продолжая пост про задания планировщика, вот список всех заданий с командами:
$tasklist = @()
Get-ScheduledTask | ForEach-Object {
$task = [xml](Export-ScheduledTask -TaskName $_.URI)
$taskdetails = New-Object -Type Psobject -Property @{
"Name" = $_.URI
"Action" = $task.Task.Actions.Exec.Command
}
$tasklist += $taskdetails
}
$tasklist | Select-Object Name,Action | Out-GridView


Разумеется, этот способ не ограничивается оснастками MMC и вообще ОС Windows. Применяйте командлет Out-GridView для анализа любых табличных наборов данных! ✌️
🛍 Как открыть магазинное приложение, если у него нет ярлыка

И в Пуске оно тоже не находится. Этот вопрос задал мне подписчик Андрей К в контексте приложения Сообщения оператора (Operator Messages). Оно показывает служебные SMS мобильного оператора на устройствах с поддержкой 4G/5G.

Как я понял, приложение само открывается для отображения входящих SMS. Но если его закрыть, то потом уже не открыть. Андрей упомянул, что приложение можно закрепить на панели задач. И для меня это уже выглядит решением ✔️

Возможно, он не хотел занимать место на панели задач, предпочитая отдельный ярлык на рабочем столе. В этом случае следует закрыть закрепленное приложение - щелкнуть значок правой кнопкой мыши, удерживая Shift и выбрать пункт "Создать ярлык" или "Отправить -> Рабочий стол" ✔️

Но допустим, что таких пунктов в меню не оказалось. Либо автор вопроса хочет запускать приложение из командной строки. Короче, этюдная задача с элементами матчасти :)

🤔 Рассуждая логически, раз приложение открывается на входящие сообщения, оно должно быть зарегистрировано для какого-то протокола. Найдем протокол - решим задачу.

1. Поищите protocol в Параметрах и откройте страницу с приложениями по умолчанию.
2. В самом низу откройте раздел для сопоставления приложений типам ссылок.
3. Найдите приложение глазами, ориентируясь на значок. (На странице есть фильтр, но только по протоколам, а его-то мы и не знаем :)

В данном случае находится URL:ms-chat. И этого достаточно для решения задачи! У вас это приложение вряд ли установлено, поэтому для наглядности я заменил протокол ms-chat: на ms-calculator:. Вот разные способы открытия:

• окно Win+R: ms-calculator:
• произвольный ярлык: explorer ms-calculator:
• командная строка: start calculator:

Да, протокол calculator: там тоже есть :)

////

ℹ️ Протоколы регистрируются в реестре. Полный список протоколов Microsoft легко отобразить с помощью #PowerShell

New-PSDrive -Name "HKCR" -PSProvider Registry -Root "HKEY_CLASSES_ROOT"
Get-Item HKCR:\ms-* | select name

Не все они будут открывать какое-то приложение. Но в большинстве случаев это сработает.

Читателям со стажем эта тема покажется знакомой. Я как-то рассказывал в канале про быстрый доступ к страницам Параметров. И там всё строится именно на протоколе ms-settings! ✌️
⚙️ Как избавиться от предупреждения в проводнике при копировании файлов на диск, отформатированный в ext4

Этот вопрос задал в чате Юрий. Он переносил файлы на внешний диск с файловой системой ext4 и получал на каждый исполняемый файл такой запрос:

Вы действительно хотите переместить файл без его свойств?

Я сходу предложил использовать консольные средства. Например, в #PowerShell можно копировать файлы и папки командлетом Copy-Item. И это сработало! ☑️

Однако Юрий не желал каждый раз лезть в консоль. Мол, он регулярно копирует файлы с локального диска на внешний. Что ж, для регулярных задач синхронизации файлов в Windows испокон веков есть прекрасная утилита командной строки robocopy! ☑️

Но и это Юрия не устроило. И сторонние файловые менеджеры тоже отметались :) Он оказался маководом и хотел амазинга просто копировать в проводнике. Он также сообщил, что нагуглил в качестве решения утилиту NTFS Stream Explorer. Но опять же, в ней требовались ручные действия перед копированием.

////

ℹ️ Здесь в самый раз подходила #классика блога Альтернативные потоки данных NTFS. Юрий нашел в ней твик реестра, но тот не помог. Тогда все-таки пришлось читать, вникать, и вскоре мы увидели примерно такую картину. Я сохранил смысл, но в качестве примера взял свой файл из папки Downloads и сократил вывод до сути.

Get-Item -Path .\*keep*exe -Stream * | 
Select-Object pschildname,stream

PSChildName Stream
----------- ------
KeePass-2.57-Setup.exe::$DATA :$DATA
KeePass-2.57-Setup.exe:SmartScreen SmartScreen


Сразу прояснилось множество деталей:
1. Альтернативный поток NTFS пишет SmartScreen.
2. Исполняемый файл скачан в Microsoft Edge, что вытекает из п. 1 и проверяется чтением потока.
3. Потока с идентификатором зоны нет. Он уже удален либо запись отключена политикой ↓
4. Твик с политикой "Не хранить сведения о зоне..." бессилен против п. 1.

Тут школьники посоветуют "отключить SmartScreen". Причем в такой формулировке неочевидно, что конкретно отключать. Однако снижение уровня безопасности (особенно у маковода :) - это не наш путь.

////

Избавиться от предупреждения в проводнике можно без твиков / политик и отключений функций безопасности. Мы знаем, что при загрузке в любом браузере файлы получают поток с идентификатором зоны. Кроме того, в Microsoft Edge к исполняемым файлам свой поток пишет SmartScreen.

Решение укладывается в одну команду. В примере потоки от SmartScreen и диспетчера вложений удаляются из всех файлов в папке "Загрузки" рекурсивно. После чего проводник уже не суетится при их копирование на ext4.

Get-ChildItem -Path $env:USERPROFILE\downloads\ -File -Recurse | Remove-Item -Stream *SmartScreen*, *Zone.Identifier*

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

-NoProfile -Ex Bypass -Command "Get-ChildItem -Path $env:USERPROFILE\downloads\ -File -Recurse | Remove-Item -Stream *SmartScreen*, *Zone.Identifier*"

Скрытие консольных окон я разбирал четыре года назад, и с тех пор ничего не изменилось. ✌️
▶️ Тонкая настройка защитника Windows с помошью PowerShell

В чате участник Demon спросил, почему защитник Windows удаляет файлы из папки, добавленной в исключения. Я запросил вывод PowerShell:

Get-MpThreat
Get-MpPreference | ft ExclusionPath

Первая команда показывает обнаруженные угрозы, а вторая - исключенные из мониторинга пути. Выяснилось, что исключена папка E:\Games, но удаленный файл лежал в одной из ее подпапок. Защитник определил его как HackTool:Win32/Crack.

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

ℹ️ В #PowerShell вы можете получить сведения о параметрах защитника командлетом Get-MpPreference. И есть еще два для установки параметров:

Add-MpPreference добавляет значения к существующему набору настроек.
Set-MpPreference задает свой набор настроек, перезаписывая текущий.

Оба поддерживают подстановочные символы * и ?. Но есть нюанс © У вас не получится добавить в исключения все дочерние папки рекурсивно так:

Add-MpPreference -ExclusionPath E:\Games\*

Это исключает только папки одного уровня вложенности. Два уровня - E:\Games\*\* и т.д. Поддерживается максимум 6 подстановочных символов. Подробнее смотрите в документации с примерами.

////

В рамках того же обсуждения Piter Pen посетовал, что защитник непоследовательно реагирует на различные угрозы. Одни отправляет в карантин, другие удаляет 🤷‍♂️

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

Текущие настройки можно посмотреть так:

Get-MpPreference | Select-Object *action


HighThreatDefaultAction : 0
LowThreatDefaultAction : 0
ModerateThreatDefaultAction : 0
SevereThreatDefaultAction : 0
UnknownThreatDefaultAction : 0


Ноль означает действие по умолчанию в зависимости от предпочтений Microsoft, заданных сигнатурами. Это написано в справке Set-MpPreference.

Там же перечислены числовые значения для семи различных действий защитника. Например, 2 - карантин, 3 - удаление, 9 - игнорирование. Это помогает понять текущий уровень настроек.

Для каждого уровня серьезности угроз вы можете задать только эти три действия↑ (остальные применяются к конкретным экземплярам по их ИД). Например, вы хотите отправлять самые серьёзные угрозы в карантин вместо удаления:

Set-MpPreference -SevereThreatDefaultAction Quarantine


////

Не стоит воздевать руки к небу, что конечным пользователям не дали тонко настроить компонент в графическом интерфейсе. Когда касается безопасности, я только за! Потому что для неопытных людей это кончается стрельбой в ногу, а то и в голову.

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

В качестве завершающего аккорда здесь идеально подходит #классика блога О роли головного мозга в защите операционной системы ✌️
▶️ О фильтрах по имени в командлетах Get-

Тут Павел Нагаев "решил упороться в шортсы" с ошибками новичков в #PowerShell (начало серии здесь). Там не совсем ошибки, а скорее неэффективные конструкции, которые можно найти у всех новичков. Потому что они так нагуглили или теперь ChatGPT им так написал.

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

Get-AppxProvisionedPackage -Online | Where-Object {$_.DisplayName -notlike '*store*' -and $_.DisplayName -notlike '*calc*' -and $_.DisplayName -notlike '*notepad*'} | Remove-AppxProvisionedPackage -Online

Вообще, команда в таком виде попала в статью, будучи предложенной в комментариях читателем. Я скопировал и вставил, но ответственности с себя не снимаю :)

📢 Именно Павел подсказал мне, что код можно существенно сократить, задействовав вместо нескольких логических операторов -and один оператор -notmatch и регулярное выражение:

Get-AppxProvisionedPackage -Online | Where-Object {$_.DisplayName -notmatch 'store|calc|notepad'} | Remove-AppxProvisionedPackage -Online

Намного проще! Также замечу, что у Where-Object уже лет дцать поддерживается такой синтаксис:

Where-Object DisplayName -notmatch 'store|calc|notepad'

Ещё проще!

////

В своем видео Павел использовал для примера выборку служб командлетом Get-Service по первой букве названия службы.

Get-Service | Where-Object {$_.Name -match "^w|^t|^f"}

И тут настала моя очередь доставлять ему про неэффективность 😎 Если уж браться рассказывать про эффективные фильтры, надо начинать со встроенных фильтров командлетов Get-!

Get-Service -Name f*,t*,w*

Это - тоже выборка по первым буквам названий служб. Фокус в том, что по имени можно фильтровать без передачи по конвейеру на Where-Object.

🤝 Павел превратил мой фидбэк в короткое видео! https://www.youtube.com/shorts/9BUdqYLagnk

Отмечу, что параметр необязательно должен называться -Name. Недавно я показывал в канале анализ запланированных заданий. Вот пример с -TaskName:

Get-ScheduledTask -TaskName devi*,sync*

////

В PowerShell очень много командлетов Get- :

Get-Command -Name Get* -CommandType Cmdlet

Не все они располагают к фильтру по имени, но тут главное - знать приём ✌️
🕒 Как предотвратить автоматическую перезагрузку после установки обновлений в Windows 11

Опубликованное в канале Расследование: кто перезагрузил Windows завершалось такими словами:

А в Windows 11 уже нет поддерживаемого способа предотвратить форсированный перезапуск после установки обновлений. Единственный обходной путь - ручное планирование перезагрузки, что позволяет отложить ее максимум на неделю.

С тех пор несколько человек доставили еще один обходной путь, который позволяет откладывать перезагрузку "бесконечно" долго. Беру в кавычки, потому что нам не дано протестировать бесконечность :)

👉 Суть способа: регулярное изменение периода активности с помощью запланированного задания. Windows не перезагружает систему в активные часы.

Это любопытный пример ситуации, когда поддерживаемыми способами система вводится в неподдерживаемое состояние. Поэтому в конце поста классическая и горячо любимая картинка 😎

Поскольку настройки периода активности задаются в реестре, скрипт несложный. Берем текущее время, добавляем к нему 18 часов (максимальный диапазон) и устанавливаем полученные значения соответственно для начала и конца периода активности. Примечательно, что параметры хранятся в разделе HKLM, но группе "Пользователи" запись туда разрешена.

▶️ Подписчик Алексей Батищев выложил на GitHub скрипт #PowerShell с установщиком и VBS (для скрытия консольных окон).

В установщик имеет смысл дописать запуск задания по факту добавления в планировщик. А в самом задании прописать -StartWhenAvailable (немедленный запуск после пропуска) и поменять частоту запусков с получаса до 12 часов.

////

Публикация не означает рекомендации или призыва к действию. Сам я предпочитаю не работать с известными уязвимостями. Поэтому доверяю перезагрузку автоматике, при необходимости задавая дату и время перезапуска вручную ✌️
😎 PowerShell vs. CMD: запуск приложений с разным окружением

У моей команды есть два бизнес-приложения, которыми люди пользуются каждый день. При этом различные версии приложений нужно регулярно запускать в разных средах тестирования, создавая необходимое окружение при старте.

⌛️ Исторически они пользовались для запуска набором батников (пакетных файлов с расширением .bat), которые им написал добрый человек. Но почему-то лишь для одного приложения. Может, для второго не попросили, либо автор не осилил обновление на лету INI-файла с конфигурацией.

Я бы тоже не осилил, поэтому переписал все на #PowerShell :) Но батники для вызова скрипта оставил. Иначе у пользователя скрипт скорее не запустится, чем запустится 🤷‍♂️

powershell.exe -NoProfile -ExecutionPolicy Bypass -File script.ps1

Недавно в команду пришёл новый мальчик, попользовался скриптами и вскорости представил нам свое решение проблемы. Приложение-лончер с графическим интерфейсом, написанное на Java! 🌺

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

Я заинтересовался технической реализацией смены окружения, и молодой человек с готовностью продемонстрировал содержимое папки resources. А в ней лежат батники, которые он написал на основе моих скриптов PowerShell и вызывает из Java-приложения 😂

Выбор победителя в этой схватке #powershellvscmd я оставляю вам ✌️