Русский
Русский
English
Статистика
Реклама

Sophia script

Закрепляем ярлыки на начальном экране в Windows 10 у текущего пользователя

06.04.2021 10:06:21 | Автор: admin

Эта статья своего рода proof of concept, как можно закрепить программно (открепить) ярлык на начальном экране для текущего пользователя без перезапуска или выхода из учетной записи. Как вы знаете, с выходом Windows 10 October 2018 Microsoft без шума закрыл доступ к API открепления (закрепления) ярлыков от начального экрана и панели задач: отныне это можно сделать лишь вручную.

Ниже приведен пример кода для закрепления (открепления) ярлыка на начальный экран, который когда-то работал. Как можете видеть, в коде используется метод получения локализорованной строки, и для этого нам необходимо знать код строки, чтобы вызвать соответствующий пункт контекстного меню. В данном пример, чтобы закрепить ярлык командной строки, мы вызываем строку с кодом 51201, Закрепить на начальном экране, из библиотеки %SystemRoot%\system32\shell32.dll.

Получить список всех локализованных строк удобнее всего через стороннюю утилиту ResourcesExtract.

# Extract a localized string from shell32.dll$Signature = @{Namespace = "WinAPI"Name = "GetStr"Language = "CSharp"MemberDefinition = @"[DllImport("kernel32.dll", CharSet = CharSet.Auto)]public static extern IntPtr GetModuleHandle(string lpModuleName);[DllImport("user32.dll", CharSet = CharSet.Auto)]internal static extern int LoadString(IntPtr hInstance, uint uID, StringBuilder lpBuffer, int nBufferMax);public static string GetString(uint strId){IntPtr intPtr = GetModuleHandle("shell32.dll");StringBuilder sb = new StringBuilder(255);LoadString(intPtr, strId, sb, sb.Capacity);return sb.ToString();}"@}if (-not ("WinAPI.GetStr" -as [type])){Add-Type @Signature -Using System.Text}# Pin to Start: 51201# Unpin from Start: 51394$LocalizedString = [WinAPI.GetStr]::GetString(51201)# Trying to pin the Command Prompt shortcut to Start$Target = Get-Item -Path "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\System Tools\Command Prompt.lnk"$Shell = New-Object -ComObject Shell.Application$Folder = $Shell.NameSpace($Target.DirectoryName)$file = $Folder.ParseName($Target.Name)$Verb = $File.Verbs() | Where-Object -FilterScript {$_.Name -eq $LocalizedString}$Verb.DoIt()

Сейчас консоль вываливается с ошибкой Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

Хотя, как можно заметить, API, конечно, отдает глагол контекстного меню Закрепить на начальном &экране, но не может его выполнить.

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

Я уже много лет поддерживаю крупнейший PowerShell-модуль для тонкой настройки Windows 10 и автоматизации рутинных задач. Подробнее можно почитать здесь. И встала задача из спортивного интереса обойти это ограничение и попробовать закрепить нужные мне ярлыки. Речь, конечно, идет по большей части о домашних пользователях, ведь в энтерпрайзе используется GPO для импорта предзаготовленного макета начального экрана и панели задач.

Мы знаем, что текущий макет начального экрана можно выгрузить в формате XML. Но даже, если его настроить должным образом, импортировать макет в профиль текущего пользователя не получится: Import-StartLayout -LayoutPath "D:\Layout.xml импортирует макеты начального экрана и панели задач только для новых пользователей.

Идея заключается в том, чтобы использовать политику Макет начального экрана (Prevent users from customizing their Start Screen), отвечающую за подгрузку предзаготовленного макета в формате XML из определенного места. Соответственно, наш хак будет состоять из следующих пунктов:

  • Выгружаем текущий макет начального экрана;

  • Парсим XML, добавляя необходимые нам ярлыки (ссылки должны вести на реально существующие ярлыки) и сохраняем;

  • С помощью политики временно выключаем возможность редактировать макет начального экрана;

  • Перезапускаем меню Пуск;

  • Программно открываем меню Пуск, чтобы в реестре сохранился его макет;

  • Выключаем политику, чтобы можно было редактировать макет начального экрана;

  • И открываем меню Пуск опять.

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

<#.SYNOPSISConfigure the Start tiles.PARAMETER ControlPanelPin the "Control Panel" shortcut to Start.PARAMETER DevicesPrintersPin the "Devices & Printers" shortcut to Start.PARAMETER PowerShellPin the "Windows PowerShell" shortcut to Start.PARAMETER UnpinAllUnpin all the Start tiles.EXAMPLE.\Pin.ps1 -Tiles ControlPanel, DevicesPrinters, PowerShell.EXAMPLE.\Pin.ps1 -UnpinAll.EXAMPLE.\Pin.ps1 -UnpinAll -Tiles ControlPanel, DevicesPrinters, PowerShell.EXAMPLE.\Pin.ps1 -UnpinAll -Tiles ControlPanel.EXAMPLE.\Pin.ps1 -Tiles ControlPanel -UnpinAll.LINKhttps://github.com/farag2/Windows-10-Sophia-Script.NOTESSeparate arguments with commaCurrent user#>[CmdletBinding()]param([Parameter(Mandatory = $false,Position = 0)][switch]$UnpinAll,[Parameter(Mandatory = $false,Position = 1)][ValidateSet("ControlPanel", "DevicesPrinters", "PowerShell")][string[]]$Tiles,[string]$StartLayout = "$PSScriptRoot\StartLayout.xml")begin{# Unpin all the Start tilesif ($UnpinAll){Export-StartLayout -Path $StartLayout -UseDesktopApplicationID[xml]$XML = Get-Content -Path $StartLayout -Encoding UTF8 -Force$Groups = $XML.LayoutModificationTemplate.DefaultLayoutOverride.StartLayoutCollection.StartLayout.Groupforeach ($Group in $Groups){# Removing all groups inside XML$Group.ParentNode.RemoveChild($Group) | Out-Null}$XML.Save($StartLayout)}}process{# Extract strings from shell32.dll using its' number$Signature = @{Namespace = "WinAPI"Name = "GetStr"Language = "CSharp"MemberDefinition = @"[DllImport("kernel32.dll", CharSet = CharSet.Auto)]public static extern IntPtr GetModuleHandle(string lpModuleName);[DllImport("user32.dll", CharSet = CharSet.Auto)]internal static extern int LoadString(IntPtr hInstance, uint uID, StringBuilder lpBuffer, int nBufferMax);public static string GetString(uint strId){IntPtr intPtr = GetModuleHandle("shell32.dll");StringBuilder sb = new StringBuilder(255);LoadString(intPtr, strId, sb, sb.Capacity);return sb.ToString();}"@}if (-not ("WinAPI.GetStr" -as [type])){Add-Type @Signature -Using System.Text}# Extract the localized "Devices and Printers" string from shell32.dll$DevicesPrinters = [WinAPI.GetStr]::GetString(30493)# We need to get the AppID because it's auto generated$Script:DevicesPrintersAppID = (Get-StartApps | Where-Object -FilterScript {$_.Name -eq $DevicesPrinters}).AppID$Parameters = @(# Control Panel hash table@{# Special name for Control PanelName = "ControlPanel"Size = "2x2"Column = 0Row = 0AppID = "Microsoft.Windows.ControlPanel"},# "Devices & Printers" hash table@{# Special name for "Devices & Printers"Name = "DevicesPrinters"Size   = "2x2"Column = 2Row    = 0AppID  = $Script:DevicesPrintersAppID},# Windows PowerShell hash table@{# Special name for Windows PowerShellName = "PowerShell"Size = "2x2"Column = 4Row = 0AppID = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe"})# Valid columns to place tiles in$ValidColumns = @(0, 2, 4)[string]$StartLayoutNS = "http://schemas.microsoft.com/Start/2014/StartLayout"# Add pre-configured hastable to XMLfunction Add-Tile{param([string]$Size,[int]$Column,[int]$Row,[string]$AppID)[string]$elementName = "start:DesktopApplicationTile"[Xml.XmlElement]$Table = $xml.CreateElement($elementName, $StartLayoutNS)$Table.SetAttribute("Size", $Size)$Table.SetAttribute("Column", $Column)$Table.SetAttribute("Row", $Row)$Table.SetAttribute("DesktopApplicationID", $AppID)$Table}if (-not (Test-Path -Path $StartLayout)){# Export the current Start layoutExport-StartLayout -Path $StartLayout -UseDesktopApplicationID}[xml]$XML = Get-Content -Path $StartLayout -Encoding UTF8 -Forceforeach ($Tile in $Tiles){switch ($Tile){ControlPanel{$ControlPanel = [WinAPI.GetStr]::GetString(12712)Write-Verbose -Message ("The `"{0}`" shortcut is being pinned to Start" -f $ControlPanel) -Verbose}DevicesPrinters{$DevicesPrinters = [WinAPI.GetStr]::GetString(30493)Write-Verbose -Message ("The `"{0}`" shortcut is being pinned to Start" -f $DevicesPrinters) -Verbose# Create the old-style "Devices and Printers" shortcut in the Start menu$Shell = New-Object -ComObject Wscript.Shell$Shortcut = $Shell.CreateShortcut("$env:APPDATA\Microsoft\Windows\Start menu\Programs\System Tools\$DevicesPrinters.lnk")$Shortcut.TargetPath = "control"$Shortcut.Arguments = "printers"$Shortcut.IconLocation = "$env:SystemRoot\system32\DeviceCenter.dll"$Shortcut.Save()Start-Sleep -Seconds 3}PowerShell{Write-Verbose -Message ("The `"{0}`" shortcut is being pinned to Start" -f "Windows PowerShell") -Verbose}}$Parameter = $Parameters | Where-Object -FilterScript {$_.Name -eq $Tile}$Group = $XML.LayoutModificationTemplate.DefaultLayoutOverride.StartLayoutCollection.StartLayout.Group | Where-Object -FilterScript {$_.Name -eq "Sophia Script"}# If the "Sophia Script" group exists in Startif ($Group){$DesktopApplicationID = ($Parameters | Where-Object -FilterScript {$_.Name -eq $Tile}).AppIDif (-not ($Group.DesktopApplicationTile | Where-Object -FilterScript {$_.DesktopApplicationID -eq $DesktopApplicationID})){# Calculate current filled columns$CurrentColumns = @($Group.DesktopApplicationTile.Column)# Calculate current free columns and take the first one$Column = (Compare-Object -ReferenceObject $ValidColumns -DifferenceObject $CurrentColumns).InputObject | Select-Object -First 1# If filled cells contain desired ones assign the first free columnif ($CurrentColumns -contains $Parameter.Column){$Parameter.Column = $Column}$Group.AppendChild((Add-Tile @Parameter)) | Out-Null}}else{# Create the "Sophia Script" group[Xml.XmlElement]$Group = $XML.CreateElement("start:Group", $StartLayoutNS)$Group.SetAttribute("Name","Sophia Script")$Group.AppendChild((Add-Tile @Parameter)) | Out-Null$XML.LayoutModificationTemplate.DefaultLayoutOverride.StartLayoutCollection.StartLayout.AppendChild($Group) | Out-Null}}$XML.Save($StartLayout)}end{# Temporarily disable changing the Start menu layoutif (-not (Test-Path -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer)){New-Item -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Force}New-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name LockedStartLayout -Value 1 -ForceNew-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name StartLayoutFile -Value $StartLayout -ForceStart-Sleep -Seconds 3# Restart the Start menuStop-Process -Name StartMenuExperienceHost -Force -ErrorAction IgnoreStart-Sleep -Seconds 3# Open the Start menu to load the new layout$wshell = New-Object -ComObject WScript.Shell$wshell.SendKeys("^{ESC}")Start-Sleep -Seconds 3# Enable changing the Start menu layoutRemove-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name LockedStartLayout -Force -ErrorAction IgnoreRemove-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name StartLayoutFile -Force -ErrorAction IgnoreRemove-Item -Path $StartLayout -ForceStop-Process -Name StartMenuExperienceHost -Force -ErrorAction IgnoreStart-Sleep -Seconds 3# Open the Start menu to load the new layout$wshell = New-Object -ComObject WScript.Shell$wshell.SendKeys("^{ESC}")}

Страница GitHub Windows 10 Sophia Script, где в том числе используется данный метод.

Огромное спасибо iNNOKENTIY21 за помощь в реализации метода.

Подробнее..

Настройки Windows 10 часть III, или куда приводят скрипты

23.04.2021 14:18:12 | Автор: admin

Здравствуйте, товарищи! Прошло чуть больше полугода после выхода предыдущей статьи о Windows 10 Sophia Script скрипте, который за прошедшие годы стал самым крупным (а их осталось всего два) опенсорс-проектом по персонализации и настройке Windows 10, а также автоматизации рутинных задач. В статье я расскажу, что изменилось с момента релиза версии, описываемой в статье от 29.09.2020, с какими трудностями мы столкнулись, и куда всё движется.


Как всё начиналось

Разработка наброска скрипта берёт своё начало в те далёкие времена, когда после года работы экономистом в отделе проектирования птицефабрики в одной организации я решил перейти в местный отдел IT.

Отдел ITОтдел IT

Перейдя на новое место, я предложил автоматизировать настройку пользовательских ОС. Так, через пару недель, появился первый прародитель данного модуля примитивный .reg-файл для настройки Windows 8.1. Но я и ему был рад, так как всё было в новинку.

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

В первый день выхода Windows 10 я сразу же "пересел" на неё, поняв, что Windows 8.1 осталась для Microsoft в прошлом. Со временем же "батник" рос, "мужал", разрастался и в какой-то момент даже стал дёргать другой интерпретатор, powershell.exe. Скорость работы падала, и я понимал, что придётся учить PowerShell, так как batch уже не удовлетворяет моим маниакальным запросам автоматизировать всё при настройке ОС.

Как сейчас помню, в феврале 2017 года я сел читать первую статью по запросу "как внести данные в реестр с помощью PowerShell". Уже к лету 2017 года я значительно продвинулся в переписывании всех имеющих функций из "батника" в новый скрипт.

Изначально, конечно, скрипт состоял лишь из одного файла с расширением .ps1. Пользователям приходилось править код и комментировать целые функции, чтобы настроить под себя. То ещё удовольствие было

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

В таком неспешном темпе разработка шла до августа 2019 года, когда я решил поделиться своими наработками здесь. Хотя я читаю Хабр с года эдак 2007-го, зарегистрировался лишь в 2016-м.

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

Мой лик, когда ожидаю приглашенияМой лик, когда ожидаю приглашения

Как сейчас помню: сижу на сеансе в кинотеатре, и приходит уведомление на почту о новом комментарии к моей статье. Так, стоп! Её одобрили?!

Я не успевал отвечать на комментарии! Это была какая-то эйфория. Какой там фильм?! меня на Хабр пригласили прямым инвайтом! Фурор! Даже код не обо...ли (а там был кровавый мрак) и вообще любезно приняли.

Мой лик, когда получил приглашениеМой лик, когда получил приглашение

Самым неожиданным поворотом стало то, что через 5 дней после публикации мне написал некий Дмитрий (@oz-zo), прочитавший моё сетование на то, что у меня не хватает знаний, чтобы сделать графическую версию скрипта, даже хотя бы на Windows.Forms. Я был приятно удивлён, что есть ещё один старый безумец. Как выяснилось, старый, но не бесполезный!

Познакомившись, мы запланировали всё сделать примерно за 3 месяца на Windows.Forms, но наше приключение затянулось больше чем на 1,5 года: лишь в этом месяце мы вышли на финишную прямую по созданию графической версии моего скрипта SophiApp. Но это уже другая история, и, когда будет что показать, я обязательно расскажу, что мы пережили за время разработки, поделившись нашими инфернальными набросками и наработками.

С того времени как я познакомился с Дмитрием, разработка пошла быстрее: он внёс огромный вклад в создание новых функций, которых не было ни у кого: все графические функции с использованием WPF и логику к ним написал именно он; я лишь объяснил, как получать данные.

Иконка Sophia ScriptИконка Sophia Script

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

Хотя Дэвиду уже нормально так, программировать он сел лишь недавно, окончив курсы. Но его программа выполняет ту задачу, для которой её и написали. Одним словом, люди пользуются.

Sophia Script WrapperSophia Script Wrapper

Что поменялось в скрипте

За время, прошедшее с момента выхода прошлой статьи в сентябре, много воды утекло. Скрипт уже и не узнать. Больше 12 000 строк кода Самые интересные изыскания пришлись на удаление UWP-приложений и закрепление ярлыков на начальный экран.

Напомню, какие версии Windows 10 поддерживает скрипт на данный момент.

Версия

Маркетинговое название

Билд

Архитектура

Издания

21H1

Spring 2021 Update

19043

x64

Home/Pro/Enterprise

20H2

October 2020 Update

19042

x64

Home/Pro/Enterprise

2004

May 2020 Update

19041

x64

Home/Pro/Enterprise

1809

LTSC Enterprise 2019

17763

x64

Enterprise

А теперь пройдёмся по всем доработанным и новым функциям.

Функции касающиеся манипуляций с UWP-приложениями

Было/СталоБыло/Стало

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

Свойство DysplayName, которое содержит локализованное имя пакета, находится лишь в одном классе (Get-AppxPackage вам никак тут не может, к сожалению):

"Windows.Management.Deployment.PackageManager"

[Windows.Management.Deployment.PackageManager, Windows.Web, ContentType = WindowsRuntime]::new().FindPackages() | Select-Object -Property DisplayName -ExpandProperty Id | Select-Object -Property Name, DisplayName
На выходе вы получите что-то вроде этого (простыню кода прячу под спойлер)
Name                                        DisplayName                                          ----                                        -----------                                          1527c705-839a-4832-9118-54d4Bd6a0c89                                                             c5e2524a-ea46-4f67-841f-6a9465d9d515        Проводник                                            E2A4F912-2574-4A75-9BB0-0D023378592B        Сопоставитель приложений                             F46D4000-FD22-4DB4-AC8E-4E1DDDE828FE        Диалоговое окно "Добавить рекомендованные папки"     Microsoft.AAD.BrokerPlugin                  Учетная запись компании или учебного заведения       Microsoft.AccountsControl                   Электронная почта и учетные записи                   Microsoft.AsyncTextService                  AsyncTextService                                     Microsoft.BioEnrollment                     Настройка Windows Hello                              Microsoft.CredDialogHost                    Диалоговое окно учетных данных                       Microsoft.ECApp                             Управление глазами                                   Microsoft.LockApp                           Экран блокировки Windows по умолчанию                Microsoft.MicrosoftEdgeDevToolsClient       Клиент средств разработчика для Microsoft Edge       Microsoft.MicrosoftEdge                                                                          Microsoft.Win32WebViewHost                  Веб-средство просмотра классических приложений       Microsoft.Windows.Apprep.ChxApp             SmartScreen Защитника Windows                        Microsoft.Windows.AssignedAccessLockApp     Приложение "Блокировка" при ограниченном доступе     Microsoft.Windows.CallingShellApp           Видеозвонки                                          Microsoft.Windows.CapturePicker             CapturePicker                                        Microsoft.Windows.CloudExperienceHost       Ваша учетная запись                                  Microsoft.Windows.ContentDeliveryManager    Содержимое, предоставленное корпорацией Майкрософт   Microsoft.Windows.NarratorQuickStart        Экранный диктор                                      Microsoft.Windows.OOBENetworkCaptivePortal  Поток портала авторизации                            Microsoft.Windows.OOBENetworkConnectionFlow Последовательность действий при сетевом подключении  Microsoft.Windows.ParentalControls          Функции семьи учетных записей Майкрософт             Microsoft.Windows.PeopleExperienceHost      Windows Shell Experience                             Microsoft.Windows.PinningConfirmationDialog PinningConfirmationDialog                            Microsoft.Windows.Search                    Windows Search                                       Microsoft.Windows.SecHealthUI               Безопасность Windows                                 Microsoft.Windows.SecureAssessmentBrowser   Тестирование                                         Microsoft.Windows.ShellExperienceHost       Windows Shell Experience                             Microsoft.Windows.StartMenuExperienceHost   Запустить                                            Microsoft.Windows.XGpuEjectDialog           Безопасное извлечение устройства                     Microsoft.XboxGameCallableUI                Xbox Game UI                                         MicrosoftWindows.Client.CBS                 Windows Feature Experience Pack                      MicrosoftWindows.UndockedDevKit             UDK Package                                          NcsiUwpApp                                  NcsiUwpApp                                           Windows.CBSPreview                          Предварительный просмотр штрихкодов Windows          windows.immersivecontrolpanel               Параметры                                            Windows.PrintDialog                         PrintDialog                                          Microsoft.Services.Store.Engagement         Microsoft Engagement Framework                       Microsoft.Services.Store.Engagement         Microsoft Engagement Framework                       Microsoft.UI.Xaml.2.0                       Microsoft.UI.Xaml.2.0                                Microsoft.VCLibs.140.00                     Microsoft Visual C++ 2015 UWP Runtime Package        Microsoft.Advertising.Xaml                  Microsoft Advertising SDK for XAML                   Microsoft.NET.Native.Framework.2.2          Microsoft .Net Native Framework Package 2.2          Microsoft.NET.Native.Framework.2.2          Microsoft .Net Native Framework Package 2.2          Microsoft.VCLibs.140.00                     Microsoft Visual C++ 2015 UWP Runtime Package        Microsoft.VCLibs.140.00                     Microsoft Visual C++ 2015 UWP Runtime Package        Microsoft.NET.Native.Runtime.2.2            Microsoft .Net Native Runtime Package 2.2            Microsoft.NET.Native.Runtime.2.2            Microsoft .Net Native Runtime Package 2.2            Microsoft.VCLibs.140.00.UWPDesktop          Microsoft Visual C++ 2015 UWP Desktop Runtime PackageMicrosoft.VCLibs.140.00.UWPDesktop          Microsoft Visual C++ 2015 UWP Desktop Runtime PackageMicrosoft.UI.Xaml.2.1                       Microsoft.UI.Xaml.2.1                                Microsoft.UI.Xaml.2.1                       Microsoft.UI.Xaml.2.1                                Microsoft.UI.Xaml.2.0                       Microsoft.UI.Xaml.2.0                                Microsoft.UI.Xaml.2.3                       Microsoft.UI.Xaml.2.3                                Microsoft.UI.Xaml.2.3                       Microsoft.UI.Xaml.2.3                                Microsoft.UI.Xaml.2.4                       Microsoft.UI.Xaml.2.4                                Microsoft.UI.Xaml.2.4                       Microsoft.UI.Xaml.2.4                                Microsoft.ScreenSketch                      Набросок на фрагменте экрана                         Microsoft.NET.Native.Framework.1.7          Microsoft .Net Native Framework Package 1.7          Microsoft.NET.Native.Framework.1.7          Microsoft .Net Native Framework Package 1.7          Microsoft.NET.Native.Runtime.1.7            Microsoft .Net Native Runtime Package 1.7            Microsoft.NET.Native.Runtime.1.7            Microsoft .Net Native Runtime Package 1.7            Microsoft.VCLibs.120.00                     Microsoft Visual C++ Runtime Package                 Microsoft.VCLibs.120.00                     Microsoft Visual C++ Runtime Package                 Microsoft.VCLibs.140.00.UWPDesktop          Microsoft Visual C++ 2015 UWP Desktop Runtime PackageMicrosoft.VCLibs.140.00.UWPDesktop          Microsoft Visual C++ 2015 UWP Desktop Runtime PackageMicrosoft.VCLibs.140.00                     Microsoft Visual C++ 2015 UWP Runtime Package        Microsoft.VCLibs.140.00                     Microsoft Visual C++ 2015 UWP Runtime Package        Microsoft.WebpImageExtension                Расширения для изображений Webp                      Microsoft.DesktopAppInstaller               Установщик приложения                                Microsoft.NET.Native.Framework.2.2          Microsoft .Net Native Framework Package 2.2          Microsoft.NET.Native.Framework.2.2          Microsoft .Net Native Framework Package 2.2          AppUp.IntelGraphicsExperience               Центр управления графикой Intel                     Microsoft.Windows.StartMenuExperienceHost   Запустить                                            Microsoft.Windows.ShellExperienceHost       Windows Shell Experience                             Microsoft.Windows.AssignedAccessLockApp     Приложение "Блокировка" при ограниченном доступе     Microsoft.WindowsTerminal                   Windows Terminal                                     Microsoft.AV1VideoExtension                 AV1 Video Extension                                  Microsoft.HEIFImageExtension                Расширения для изображений HEIF                      Microsoft.Windows.Photos                    Фотографии (Майкрософт)                              Microsoft.UI.Xaml.2.5                       Microsoft.UI.Xaml.2.5                                Microsoft.UI.Xaml.2.5                       Microsoft.UI.Xaml.2.5                                Microsoft.WindowsStore                      Microsoft Store                                      Microsoft.StorePurchaseApp                  Узел для покупок в Store                             Microsoft.LanguageExperiencePackru-RU       Пакет локализованного интерфейса на русском          Microsoft.MicrosoftEdge                     Microsoft Edge                                       Microsoft.VP9VideoExtensions                Расширения для VP9-видео                             MicrosoftWindows.Client.WebExperience       Windows Web Experience Pack                          Microsoft.WebMediaExtensions                Расширения для интернет-мультимедиа                  Microsoft.HEVCVideoExtension                Расширения для видео HEVC от производителя устройстваMicrosoftWindows.Client.CBS                 Windows Feature Experience Pack                      Microsoft.MicrosoftEdge.Stable              Microsoft Edge     
Первые попытки переписать функцию удаления UWP-пакетовПервые попытки переписать функцию удаления UWP-пакетов

Хоть на картинке и не видно, но кнопка "Для всех пользователей" была тоже полностью переписана. Раньше она совершенно неправильно работала. Сейчас же её логика приведена к должному функционалу. По умолчанию при загрузке формы отображается список приложений для текущего пользователя (все системные пакеты и Microsoft Store исключены из списка, так что удалить хоть что-то важное не получится никак, в отличие, кстати, от всех других скриптов в Интернете). При нажатии на кнопку "Для всех пользователей" происходит динамическая перегенерация списка с учётом установленных пакетов во всех учётных записях. То есть вы можете удалить все приложения для текущего пользователя, и форма отобразится пустой, но при запуске функции с ключом "-ForAllUsers" отобразится список пакетов для всех учётных записей.

Как-то меня попросили добавить поддержку PowerShell 7. И всё это было бы смешно, когда бы не было так грустно

Во-первых, ни для кого не будет секретом, что, хотя в PowerShell 7 исправили очень много багов, в нынешнем виде очень далёк от финальной версии, ведь там до сих пор даже не работает командлет Get-ComputerRestorePoint из коробки. И (в качестве временного решения) Microsoft предложил загружать в сессию недостающие модули из папки PowerShell 5.1, используя аргумент -UseWindowsPowerShell.

Таким образом, мне приходится загружать модули Microsoft.PowerShell.Management, PackageManagement, Appx, чтобы воссоздать работоспособность скрипта на PowerShell 7:

Import-Module -Name Microsoft.PowerShell.Management, PackageManagement, Appx -UseWindowsPowerShell

Во-вторых, код для получения локализованных имен UWP-пакетов не работает в PowerShell 7 вообще, так как Microsoft решил не включать библиотеки WinRT в релизы PowerShell 7, но вынес разработку на отдельные ресурсы: WinRT и Windows.SDK. Это упомянул и Steven Lee в обсуждении на GitHub, а также уведомил, что команда PowerShell решила не включать в дальнейшем в релизы эти библиотеки. Поэтому, чтобы вызвать необходимые API, мне приходится хранить в папке две библиотеки по 26 МБ и 284 КБ. Тут остаётся лишь поставить мем с пингвином.

Код для получения локализованных имен UWP-пакетов на PowerShell 7 выглядит так:

Add-Type -AssemblyName "$PSScriptRoot\Libraries\WinRT.Runtime.dll"Add-Type -AssemblyName "$PSScriptRoot\Libraries\Microsoft.Windows.SDK.NET.dll"$AppxPackages = Get-AppxPackage -PackageTypeFilter Bundle -AllUsers$PackagesIds = [Windows.Management.Deployment.PackageManager]::new().FindPackages().AdditionalTypeData[[Collections.IEnumerable].TypeHandle] | Select-Object -Property DisplayName -ExpandProperty Id | Select-Object -Property Name, DisplayNameforeach ($AppxPackage in $AppxPackages){$PackageId = $PackagesIds | Where-Object -FilterScript {$_.Name -eq $AppxPackage.Name}if (-not $PackageId){continue}[PSCustomObject]@{Name = $AppxPackage.NamePackageFullName = $AppxPackage.PackageFullNameDisplayName = $PackageId.DisplayName}}

На выходе будет что-то вроде:

Name                                         PackageFullName                                                                DisplayName----                                         ---------------                                                                -----------RealtekSemiconductorCorp.RealtekAudioControl RealtekSemiconductorCorp.RealtekAudioControl_1.1.137.0_neutral_~_dt26b99r8h8gj Realtek Audio ControlMicrosoft.MicrosoftStickyNotes               Microsoft.MicrosoftStickyNotes_3.7.142.0_neutral_~_8wekyb3d8bbwe               Microsoft Sticky NotesMicrosoft.ScreenSketch                       Microsoft.ScreenSketch_2020.814.2355.0_neutral_~_8wekyb3d8bbwe                 Набросок на фрагменте экранаMicrosoft.WindowsCalculator                  Microsoft.WindowsCalculator_2020.2008.2.0_neutral_~_8wekyb3d8bbwe              Windows CalculatorAppUp.IntelGraphicsExperience                AppUp.IntelGraphicsExperience_1.100.3282.0_neutral_~_8j3eq9eme6ctt             Центр управления графикой IntelMicrosoft.MicrosoftSolitaireCollection       Microsoft.MicrosoftSolitaireCollection_4.7.10142.0_neutral_~_8wekyb3d8bbwe     Microsoft Solitaire CollectionMicrosoft.DesktopAppInstaller                Microsoft.DesktopAppInstaller_2020.1112.20.0_neutral_~_8wekyb3d8bbwe           Установщик приложенияMicrosoft.WindowsStore                       Microsoft.WindowsStore_12101.1001.1413.0_neutral_~_8wekyb3d8bbwe               Microsoft StoreMicrosoft.Windows.Photos                     Microsoft.Windows.Photos_2020.20120.4004.0_neutral_~_8wekyb3d8bbwe             Фотографии (Майкрософт)Microsoft.WebMediaExtensions                 Microsoft.WebMediaExtensions_1.0.40471.0_neutral_~_8wekyb3d8bbwe               Расширения для интернет-мультимMicrosoft.WindowsCamera                      Microsoft.WindowsCamera_2021.105.10.0_neutral_~_8wekyb3d8bbwe                  Камера WindowsMicrosoft.StorePurchaseApp                   Microsoft.StorePurchaseApp_12103.1001.813.0_neutral_~_8wekyb3d8bbwe            Узел для покупок в StoreMicrosoft.WindowsTerminal                    Microsoft.WindowsTerminal_2021.413.2245.0_neutral_~_8wekyb3d8bbwe              Windows TerminalMicrosoft.WindowsTerminalPreview             Microsoft.WindowsTerminalPreview_2021.413.2303.0_neutral_~_8wekyb3d8bbwe       Windows Terminal Preview

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

Восстановление удаленных UWP-приложений для текущего пользователяВосстановление удаленных UWP-приложений для текущего пользователя

Код для получения общего списка всех манифестов выглядит так (можете даже выполнить, если, конечно, не удалили все UWP-приложения):

$Bundles = (Get-AppXPackage -PackageTypeFilter Framework -AllUsers).PackageFullNameGet-ChildItem -Path "HKLM:\SOFTWARE\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\PackageRepository\Packages" | ForEach-Object -Process {Get-ItemProperty -Path $_.PSPath} | Where-Object -FilterScript {$_.Path -match "Program Files"} | Where-Object -FilterScript {$_.PSChildName -notin $Bundles} | Where-Object -FilterScript {$_.Path -match "x64"} | ForEach-Object -Process {"$($_.Path)\AppxManifest.xml"}
И вы увидите что-то вроде:
C:\Program Files\WindowsApps\A025C540.Yandex.Music_4.40.7713.0_x64__vfvw9svesycw6\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.549981C3F5F10_2.2103.17603.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.BingNews_1.0.6.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.BingWeather_1.0.6.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.11.10771.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.GamingApp_1.0.1.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.GetHelp_10.2102.40951.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.Getstarted_10.2.40751.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.HEIFImageExtension_1.0.40978.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.MicrosoftOfficeHub_18.2008.12711.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.MicrosoftSolitaireCollection_4.9.4072.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.MicrosoftStickyNotes_1.8.15.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.Paint_10.2103.1.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.People_10.1909.12456.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.PowerAutomateDesktop_1.0.31.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.ScreenSketch_11.2103.13.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.SkypeApp_14.53.77.0_x64__kzf8qxf38zg5c\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.StorePurchaseApp_12103.1001.8.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.Todos_0.41.4902.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.VP9VideoExtensions_1.0.40631.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WebMediaExtensions_1.0.40831.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WebpImageExtension_1.0.32731.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.Windows.Photos_2021.21030.17018.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WindowsAlarms_1.0.38.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WindowsCalculator_10.2103.8.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WindowsCamera_2020.503.58.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\microsoft.windowscommunicationsapps_16005.13426.20688.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WindowsFeedbackHub_1.2009.10531.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WindowsMaps_1.0.27.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WindowsNotepad_10.2103.6.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WindowsSoundRecorder_1.0.42.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WindowsStore_12103.1001.11.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.6.10571.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.Xbox.TCUI_1.23.28002.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.XboxGameOverlay_1.54.4001.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.XboxGamingOverlay_5.621.4072.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.XboxIdentityProvider_12.67.21001.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.XboxSpeechToTextOverlay_1.21.13002.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.YourPhone_1.21022.202.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.ZuneMusic_10.21012.10511.0_x64__8wekyb3d8bbwe\AppxManifest.xmlC:\Program Files\WindowsApps\Microsoft.ZuneVideo_10.21021.10311.0_x64__8wekyb3d8bbwe\AppxManifest.xmlPS C:\Windows\system32\WindowsPowerShell\v1.0> 

Но нам надо сопоставить имя пакета, его локализованное имя в системе и путь до манифеста. Искать будем среди пакетов, которые имеют статус "Staged", то есть готовы к восстановлению.

# Тут нельзя напрямую вписать -PackageTypeFilter Bundle, так как иначе не выдается нужное свойство InstallLocation. Только сравнивать с $Bundles$Bundles = (Get-AppXPackage -PackageTypeFilter Bundle -AllUsers).Name$AppxPackages = Get-AppxPackage -AllUsers | Where-Object -FilterScript {$_.PackageUserInformation -match "Staged"} | Where-Object -FilterScript {$_.Name -in $Bundles}$PackagesIds = [Windows.Management.Deployment.PackageManager, Windows.Web, ContentType = WindowsRuntime]::new().FindPackages() | Select-Object -Property DisplayName -ExpandProperty Id | Select-Object -Property Name, DisplayNameforeach ($AppxPackage in $AppxPackages){$PackageId = $PackagesIds | Where-Object -FilterScript {$_.Name -eq $AppxPackage.Name}if (-not $PackageId){continue}[PSCustomObject]@{Name            = $AppxPackage.NamePackageFullName = $AppxPackage.PackageFullNameDisplayName     = $PackageId.DisplayNameAppxManifest    = "$($AppxPackage.InstallLocation)\AppxManifest.xml"}}

Ну, а дальше уже дело техники. Кстати, если вам надо восстановить все возможные пакеты без разбора, то в этом вам поможет.

# Re-register all UWP apps$Bundles = (Get-AppXPackage -PackageTypeFilter Framework -AllUsers).PackageFullNameGet-ChildItem -Path "HKLM:\SOFTWARE\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\PackageRepository\Packages" | ForEach-Object -Process {Get-ItemProperty -Path $_.PSPath} | Where-Object -FilterScript {$_.Path -match "Program Files"} | Where-Object -FilterScript {$_.PSChildName -notin $Bundles} | Where-Object -FilterScript {$_.Path -match "x64"} | ForEach-Object -Process {"$($_.Path)\AppxManifest.xml"} | Add-AppxPackage -Register -ForceApplicationShutdown -ForceUpdateFromAnyVersion -DisableDevelopmentMode -Verbose# Check for UWP apps updatesGet-CimInstance -Namespace "Root\cimv2\mdm\dmmap" -ClassName "MDM_EnterpriseModernAppManagement_AppManagement01" | Invoke-CimMethod -MethodName UpdateScanMethod

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

Точно таким же способом можно выводить список локализованных имён компонентов Windows и дополнительных компонентов.w

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

Get-WindowsOptionalFeature -Online | ForEach-Object -Process {Get-WindowsOptionalFeature -FeatureName $_.FeatureName -Online} | Select-Object -Property FeatureName, DisplayName | Format-Table -AutoSize
БылоБылоСталоСталоНаши лики, когда, наконец, всё заработалоНаши лики, когда, наконец, всё заработало

Интернационализация скрипта

На необходимость этой фичи обратил внимание @FrankSinatraв комментариях. Интернационализация позволяет избавиться от страшной конструкции вида

if ($RU){}else{}

Отныне в корне папки скрипта находятся папки с названием кода локализации: например, ru-RU, en-US и так далее, где внутри находится файл локализации вида UnsupportedOSBitness = The script supports Windows 10 x64 only. Получить значение локализации можно командой $PSUICulture.

Соответственно, чтобы это всё заработало, мы импортируем указанные локализационные файлы, сохраняя строки в переменную так, чтобы можно было вызывать их в скрипте:

# Sophia.psd1ConvertFrom-StringData -StringData @'UnsupportedOSBitness = The script supports Windows 10 x64 only'@# Sophia.ps1Import-LocalizedData -BindingVariable Global:Localization -FileName Sophia$Localization.UnsupportedOSBitness

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

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

Закрепление ярлыков на начальном экране

Используется при этом чистый PowerShell. Изначально я использовал стороннюю программу syspin, но возникло желание всё-таки избавиться от неё. Как вы знаете, с выходом Windows 10 October 2018 Microsoft без шума закрыл доступ к API открепления (закрепления) ярлыков от начального экрана и панели задач: отныне это можно сделать лишь вручную.

Ниже приведён пример кода для закрепления (открепления) ярлыка на начальный экран, который когда-то работал. Как можете видеть, в коде используется метод получения локализованной строки, и для этого нам необходимо знать код строки, чтобы вызвать соответствующий пункт контекстного меню. В данном примере, чтобы закрепить ярлык командной строки, мы вызываем строку с кодом 51201, Закрепить на начальном экране, из библиотеки %SystemRoot%\system32\shell32.dll.

Получить список всех локализованных строк удобнее всего через стороннюю утилиту ResourcesExtract.

Попытка закрепить ярлык командной строки устаревшим методом:

# Extract a localized string from shell32.dll$Signature = @{Namespace = "WinAPI"Name = "GetStr"Language = "CSharp"MemberDefinition = @"[DllImport("kernel32.dll", CharSet = CharSet.Auto)]public static extern IntPtr GetModuleHandle(string lpModuleName);[DllImport("user32.dll", CharSet = CharSet.Auto)]internal static extern int LoadString(IntPtr hInstance, uint uID, StringBuilder lpBuffer, int nBufferMax);public static string GetString(uint strId){IntPtr intPtr = GetModuleHandle("shell32.dll");StringBuilder sb = new StringBuilder(255);LoadString(intPtr, strId, sb, sb.Capacity);return sb.ToString();}"@}if (-not ("WinAPI.GetStr" -as [type])){Add-Type @Signature -Using System.Text}# Pin to Start: 51201# Unpin from Start: 51394$LocalizedString = [WinAPI.GetStr]::GetString(51201)# Trying to pin the Command Prompt shortcut to Start$Target = Get-Item -Path "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\System Tools\Command Prompt.lnk"$Shell = New-Object -ComObject Shell.Application$Folder = $Shell.NameSpace($Target.DirectoryName)$file = $Folder.ParseName($Target.Name)$Verb = $File.Verbs() | Where-Object -FilterScript {$_.Name -eq $LocalizedString}$Verb.DoIt()

Сейчас консоль вываливается с ошибкой Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED).)

Хотя, как можно заметить, API, конечно, отдаёт глагол контекстного меню Закрепить на начальном &экране, но не может его выполнить.

Мы знаем, что текущий макет начального экрана можно выгрузить в формате XML. Но, даже если его настроить должным образом, импортировать макет в профиль текущего пользователя не получится: Import-StartLayout -LayoutPath D:\Layout.xml импортирует макеты начального экрана и панели задач только для новых пользователей.

Идея заключается в том, чтобы использовать политику Макет начального экрана (Prevent users from customizing their Start Screen), отвечающую за подгрузку предзаготовленного макета в формате XML из определённого места. Таким образом, наш хак будет состоять из следующих пунктов:

  • Выгружаем текущий макет начального экрана.

  • Парсим XML, добавляя необходимые нам ярлыки (ссылки должны вести на реально существующие ярлыки), и сохраняем.

  • С помощью политики временно выключаем возможность редактировать макет начального экрана.

  • Перезапускаем меню Пуск.

  • Программно открываем меню Пуск, чтобы в реестре сохранился его макет.

  • Выключаем политику, чтобы можно было редактировать макет начального экрана.

  • И открываем меню Пуск опять.

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

Код целиком:
<#.SYNOPSISConfigure the Start tiles.PARAMETER ControlPanelPin the "Control Panel" shortcut to Start.PARAMETER DevicesPrintersPin the "Devices & Printers" shortcut to Start.PARAMETER PowerShellPin the "Windows PowerShell" shortcut to Start.PARAMETER UnpinAllUnpin all the Start tiles.EXAMPLE.\Pin.ps1 -Tiles ControlPanel, DevicesPrinters, PowerShell.EXAMPLE.\Pin.ps1 -UnpinAll.EXAMPLE.\Pin.ps1 -UnpinAll -Tiles ControlPanel, DevicesPrinters, PowerShell.EXAMPLE.\Pin.ps1 -UnpinAll -Tiles ControlPanel.EXAMPLE.\Pin.ps1 -Tiles ControlPanel -UnpinAll.LINKhttps://github.com/farag2/Windows-10-Sophia-Script.NOTESSeparate arguments with commaCurrent user#>[CmdletBinding()]param([Parameter(Mandatory = $false,Position = 0)][switch]$UnpinAll,[Parameter(Mandatory = $false,Position = 1)][ValidateSet("ControlPanel", "DevicesPrinters", "PowerShell")][string[]]$Tiles,[string]$StartLayout = "$PSScriptRoot\StartLayout.xml")begin{# Unpin all the Start tilesif ($UnpinAll){Export-StartLayout -Path $StartLayout -UseDesktopApplicationID[xml]$XML = Get-Content -Path $StartLayout -Encoding UTF8 -Force$Groups = $XML.LayoutModificationTemplate.DefaultLayoutOverride.StartLayoutCollection.StartLayout.Groupforeach ($Group in $Groups){# Removing all groups inside XML$Group.ParentNode.RemoveChild($Group) | Out-Null}$XML.Save($StartLayout)}}process{# Extract strings from shell32.dll using its' number$Signature = @{Namespace = "WinAPI"Name = "GetStr"Language = "CSharp"MemberDefinition = @"[DllImport("kernel32.dll", CharSet = CharSet.Auto)]public static extern IntPtr GetModuleHandle(string lpModuleName);[DllImport("user32.dll", CharSet = CharSet.Auto)]internal static extern int LoadString(IntPtr hInstance, uint uID, StringBuilder lpBuffer, int nBufferMax);public static string GetString(uint strId){IntPtr intPtr = GetModuleHandle("shell32.dll");StringBuilder sb = new StringBuilder(255);LoadString(intPtr, strId, sb, sb.Capacity);return sb.ToString();}"@}if (-not ("WinAPI.GetStr" -as [type])){Add-Type @Signature -Using System.Text}# Extract the localized "Devices and Printers" string from shell32.dll$DevicesPrinters = [WinAPI.GetStr]::GetString(30493)# We need to get the AppID because it's auto generated$Script:DevicesPrintersAppID = (Get-StartApps | Where-Object -FilterScript {$_.Name -eq $DevicesPrinters}).AppID$Parameters = @(# Control Panel hash table@{# Special name for Control PanelName = "ControlPanel"Size = "2x2"Column = 0Row = 0AppID = "Microsoft.Windows.ControlPanel"},# "Devices & Printers" hash table@{# Special name for "Devices & Printers"Name = "DevicesPrinters"Size   = "2x2"Column = 2Row    = 0AppID  = $Script:DevicesPrintersAppID},# Windows PowerShell hash table@{# Special name for Windows PowerShellName = "PowerShell"Size = "2x2"Column = 4Row = 0AppID = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe"})# Valid columns to place tiles in$ValidColumns = @(0, 2, 4)[string]$StartLayoutNS = "http://schemas.microsoft.com/Start/2014/StartLayout"# Add pre-configured hastable to XMLfunction Add-Tile{param([string]$Size,[int]$Column,[int]$Row,[string]$AppID)[string]$elementName = "start:DesktopApplicationTile"[Xml.XmlElement]$Table = $xml.CreateElement($elementName, $StartLayoutNS)$Table.SetAttribute("Size", $Size)$Table.SetAttribute("Column", $Column)$Table.SetAttribute("Row", $Row)$Table.SetAttribute("DesktopApplicationID", $AppID)$Table}if (-not (Test-Path -Path $StartLayout)){# Export the current Start layoutExport-StartLayout -Path $StartLayout -UseDesktopApplicationID}[xml]$XML = Get-Content -Path $StartLayout -Encoding UTF8 -Forceforeach ($Tile in $Tiles){switch ($Tile){ControlPanel{$ControlPanel = [WinAPI.GetStr]::GetString(12712)Write-Verbose -Message ("The `"{0}`" shortcut is being pinned to Start" -f $ControlPanel) -Verbose}DevicesPrinters{$DevicesPrinters = [WinAPI.GetStr]::GetString(30493)Write-Verbose -Message ("The `"{0}`" shortcut is being pinned to Start" -f $DevicesPrinters) -Verbose# Create the old-style "Devices and Printers" shortcut in the Start menu$Shell = New-Object -ComObject Wscript.Shell$Shortcut = $Shell.CreateShortcut("$env:APPDATA\Microsoft\Windows\Start menu\Programs\System Tools\$DevicesPrinters.lnk")$Shortcut.TargetPath = "control"$Shortcut.Arguments = "printers"$Shortcut.IconLocation = "$env:SystemRoot\system32\DeviceCenter.dll"$Shortcut.Save()Start-Sleep -Seconds 3}PowerShell{Write-Verbose -Message ("The `"{0}`" shortcut is being pinned to Start" -f "Windows PowerShell") -Verbose}}$Parameter = $Parameters | Where-Object -FilterScript {$_.Name -eq $Tile}$Group = $XML.LayoutModificationTemplate.DefaultLayoutOverride.StartLayoutCollection.StartLayout.Group | Where-Object -FilterScript {$_.Name -eq "Sophia Script"}# If the "Sophia Script" group exists in Startif ($Group){$DesktopApplicationID = ($Parameters | Where-Object -FilterScript {$_.Name -eq $Tile}).AppIDif (-not ($Group.DesktopApplicationTile | Where-Object -FilterScript {$_.DesktopApplicationID -eq $DesktopApplicationID})){# Calculate current filled columns$CurrentColumns = @($Group.DesktopApplicationTile.Column)# Calculate current free columns and take the first one$Column = (Compare-Object -ReferenceObject $ValidColumns -DifferenceObject $CurrentColumns).InputObject | Select-Object -First 1# If filled cells contain desired ones assign the first free columnif ($CurrentColumns -contains $Parameter.Column){$Parameter.Column = $Column}$Group.AppendChild((Add-Tile @Parameter)) | Out-Null}}else{# Create the "Sophia Script" group[Xml.XmlElement]$Group = $XML.CreateElement("start:Group", $StartLayoutNS)$Group.SetAttribute("Name","Sophia Script")$Group.AppendChild((Add-Tile @Parameter)) | Out-Null$XML.LayoutModificationTemplate.DefaultLayoutOverride.StartLayoutCollection.StartLayout.AppendChild($Group) | Out-Null}}$XML.Save($StartLayout)}end{# Temporarily disable changing the Start menu layoutif (-not (Test-Path -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer)){New-Item -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Force}New-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name LockedStartLayout -Value 1 -ForceNew-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name StartLayoutFile -Value $StartLayout -ForceStart-Sleep -Seconds 3# Restart the Start menuStop-Process -Name StartMenuExperienceHost -Force -ErrorAction IgnoreStart-Sleep -Seconds 3# Open the Start menu to load the new layout$wshell = New-Object -ComObject WScript.Shell$wshell.SendKeys("^{ESC}")Start-Sleep -Seconds 3# Enable changing the Start menu layoutRemove-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name LockedStartLayout -Force -ErrorAction IgnoreRemove-ItemProperty -Path HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer -Name StartLayoutFile -Force -ErrorAction IgnoreRemove-Item -Path $StartLayout -ForceStop-Process -Name StartMenuExperienceHost -Force -ErrorAction IgnoreStart-Sleep -Seconds 3# Open the Start menu to load the new layout$wshell = New-Object -ComObject WScript.Shell$wshell.SendKeys("^{ESC}")}

Создаваемые задания в планировщике заданий

Для начала разберём две задачи по автоматизации очистки папок %TEMP% и %SystemRoot%\SoftwareDistribution\Download. Эти папки полезно очищать по расписанию, чтобы они не разрастались. На текущий момент папка временных файлов самоочищается раз в 60 дней, а папка, куда скачиваются установочные файлы для обновлений, раз в 90 дней.

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

Windows 10 позволяет генерировать такие тосты очень просто. Пример всплывающего тоста, как на картинке выше:

[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null[xml]$ToastTemplate = @"<toast duration="Long"><visual><binding template="ToastGeneric"><text>Уведомление</text><group><subgroup><text hint-style="body" hint-wrap="true">Кэш обновлений Windows успешно удален</text></subgroup></group></binding></visual><audio src="ms-winsoundevent:notification.default" /></toast>"@$ToastXml = [Windows.Data.Xml.Dom.XmlDocument]::New()$ToastXml.LoadXml($ToastTemplate.OuterXml)$ToastMessage = [Windows.UI.Notifications.ToastNotification]::New($ToastXML)[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel").Show($ToastMessage)

В вызове CreateToastNotifier можно указывать приложение, иконка которого будет отображаться в верхнем левом углу тоста и которое будет открываться при нажатии на тост. В данном случае я использовал windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel "Настройки". Но вы вольны указать любые приложения. Узнать список всех установленных приложений и их AppID нам поможет команда Get-StartApps.

Напомню, что задача по очистке папки для временных файлов удаляет лишь файлы старше суток:

Get-ChildItem -Path $env:TEMP -Recurse -Force | Where-Object {$_.CreationTime -lt (Get-Date).AddDays(-1)} | Remove-Item -Recurse -Force

А задача по очистке папки %SystemRoot%\SoftwareDistribution\Download ждёт остановку службы wuauserv (Центр обновления Windows), чтобы в дальнейшем очистить папку

(Get-Service -Name wuauserv).WaitForStatus('Stopped', '01:00:00')

С заданием по запуску очистки диска и DISM с аргументами всё гораздо веселее. Изначально стояла задача просто запускать предзаготовленный пресет настроек для очистки диска и очистку ненужных обновлений, используя DISM: dism.exe /Online /English /Cleanup-Image /StartComponentCleanup /NoRestart.

Но пришлось решать, как заставить задание запускать очистку диска, сворачивать его окно, а потом также минимизировать окно консоли с запущенным DISM.

Сложность состоит в том, что при запуске очистки диска сначала открывается первое окошко со сканированием того, что можно очистить, потом оно закрывается, и только после этого открывается новое окошко (с новым MainWindowHandle) уже непосредственно с очисткой.

Если первое окошко достаточно легко свернуть:

$ProcessInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo$ProcessInfo.FileName = "$env:SystemRoot\system32\cleanmgr.exe"$ProcessInfo.Arguments = "/sagerun:1337"$ProcessInfo.UseShellExecute = $true$ProcessInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Minimized

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

только MainWindowHandle окна может помочь
Get-Process -Name cleanmgr | Stop-Process -ForceGet-Process -Name Dism | Stop-Process -ForceGet-Process -Name DismHost | Stop-Process -Force$ProcessInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo$ProcessInfo.FileName = "$env:SystemRoot\system32\cleanmgr.exe"$ProcessInfo.Arguments = "/sagerun:1337"$ProcessInfo.UseShellExecute = $true$ProcessInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Minimized$Process = New-Object -TypeName System.Diagnostics.Process$Process.StartInfo = $ProcessInfo$Process.Start() | Out-NullStart-Sleep -Seconds 3[int]$SourceMainWindowHandle = (Get-Process -Name cleanmgr | Where-Object -FilterScript {$_.PriorityClass -eq "BelowNormal"}).MainWindowHandlefunction MinimizeWindow{[CmdletBinding()]param([Parameter(Mandatory = $true)]$Process)$ShowWindowAsync = @{Namespace = "WinAPI"Name = "Win32ShowWindowAsync"Language = "CSharp"MemberDefinition = @'[DllImport("user32.dll")]public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'@}if (-not ("WinAPI.Win32ShowWindowAsync" -as [type])){Add-Type @ShowWindowAsync}$MainWindowHandle = (Get-Process -Name $Process | Where-Object -FilterScript {$_.PriorityClass -eq "BelowNormal"}).MainWindowHandle[WinAPI.Win32ShowWindowAsync]::ShowWindowAsync($MainWindowHandle, 2)}while ($true){[int]$CurrentMainWindowHandle = (Get-Process -Name cleanmgr | Where-Object -FilterScript {$_.PriorityClass -eq "BelowNormal"}).MainWindowHandleif ($SourceMainWindowHandle -ne $CurrentMainWindowHandle){MinimizeWindow -Process cleanmgrbreak}Start-Sleep -Milliseconds 5}$ProcessInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo$ProcessInfo.FileName = "$env:SystemRoot\system32\dism.exe"$ProcessInfo.Arguments = "/Online /English /Cleanup-Image /StartComponentCleanup /NoRestart"$ProcessInfo.UseShellExecute = $true$ProcessInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Minimized$Process = New-Object -TypeName System.Diagnostics.Process$Process.StartInfo = $ProcessInfo$Process.Start() | Out-Null

Как видно из кода, $SourceMainWindowHandle первого окна ждёт, пока появится $CurrentMainWindowHandle второго окна, и, если, они не равны, то можно минимизировать новое окно. Дальше уже можно запускать DISM с ключами.

Но на этом я не остановился. Понял, что, возможно, пользователю будет неудобно, что за него решают, когда запускается такая задача (которая иногда может потребовать достаточное количество времени). Поэтому пришла идея сделать интерактивный всплывающий тост!

Как видно на скриншоте, пользователю предоставляются на выбор 3 варианта развития событий: отложить вопрос на 1, 30 минут или 4 часа, полностью отклонить предложение (тогда задача запустится через 30 дней) или запустить.

Как устроено это окно. Это всё тот же тост, но, чтобы создать кнопку, запускающую что-либо, кроме открытия страницы в браузере, необходимо сначала зарегистрировать новый протокол. В примере ниже показывается, как я регистрирую протокол WindowsCleanup:

if (-not (Test-Path -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup)){New-Item -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup -Force}New-ItemProperty -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup -Name "(default)" -PropertyType String -Value "URL:WindowsCleanup" -ForceNew-ItemProperty -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup -Name "URL Protocol" -Value "" -ForceNew-ItemProperty -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup -Name EditFlags -PropertyType DWord -Value 2162688 -Forceif (-not (Test-Path -Path Registry::HKEY_CLASSES_ROOT\shell\open\command)){New-item -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup\shell\open\command -Force}# If "Run" clicked run the "Windows Cleanup" taskNew-ItemProperty -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup\shell\open\command -Name "(default)" -PropertyType String -Value 'powershell.exe -Command "& {Start-ScheduledTask -TaskPath ''\Sophia Script\'' -TaskName ''Windows Cleanup''}"' -Force

А потом привязываю его на кнопку запуска:

<action arguments="WindowsCleanup:" content="$($Localization.Run)" activationType="protocol"/>

Всё вместе выглядит так:
# Persist the Settings notifications to prevent to immediately disappear from Action Centerif (-not (Test-Path -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel")){New-Item -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel" -Force}New-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel" -Name ShowInActionCenter -PropertyType DWord -Value 1 -Force# Register the "WindowsCleanup" protocol to be able to run the scheduled task upon clicking on the "Run" buttonif (-not (Test-Path -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup)){New-Item -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup -Force}New-ItemProperty -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup -Name "(default)" -PropertyType String -Value "URL:WindowsCleanup" -ForceNew-ItemProperty -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup -Name "URL Protocol" -Value "" -ForceNew-ItemProperty -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup -Name EditFlags -PropertyType DWord -Value 2162688 -Forceif (-not (Test-Path -Path Registry::HKEY_CLASSES_ROOT\shell\open\command)){New-item -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup\shell\open\command -Force}# If "Run" clicked run the "Windows Cleanup" taskNew-ItemProperty -Path Registry::HKEY_CLASSES_ROOT\WindowsCleanup\shell\open\command -Name "(default)" -PropertyType String -Value 'powershell.exe -Command "& {Start-ScheduledTask -TaskPath ''\Sophia Script\'' -TaskName ''Windows Cleanup''}"' -Force[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null[xml]$ToastTemplate = @"<toast duration="Long" scenario="reminder"><visual><binding template="ToastGeneric"><text>$($Localization.CleanupTaskNotificationTitle)</text><group><subgroup><text hint-style="title" hint-wrap="true">$($Localization.CleanupTaskNotificationEventTitle)</text></subgroup></group><group><subgroup><text hint-style="body" hint-wrap="true">$($Localization.CleanupTaskNotificationEvent)</text></subgroup></group></binding></visual><audio src="ms-winsoundevent:notification.default" /><actions><input id="SnoozeTimer" type="selection" title="$($Localization.CleanupTaskNotificationSnoozeInterval)" defaultInput="1"><selection id="1" content="$($Localization.Minute)" /><selection id="30" content="$($Localization.Minute)" /><selection id="240" content="$($Localization.Minute)" /></input><action activationType="system" arguments="snooze" hint-inputId="SnoozeTimer" content="" id="test-snooze"/><action arguments="WindowsCleanup:" content="$($Localization.Run)" activationType="protocol"/><action arguments="dismiss" content="" activationType="system"/></actions></toast>"@$ToastXml = [Windows.Data.Xml.Dom.XmlDocument]::New()$ToastXml.LoadXml($ToastTemplate.OuterXml)$ToastMessage = [Windows.UI.Notifications.ToastNotification]::New($ToastXML)[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel").Show($ToastMessage)

Функция ассоциации файлов

Как известно, начиная с Windows 8 невозможно самостоятельно ассоциировать какое-либо расширение с программой, не вычислив правильный хэш. Как выяснилось, Microsoft проводит манипуляции с захардоженной строкой "User Choice set via Windows User Experience {D18B6DD5-6124-4341-9318-804003BAFA0B}".

Пользователь Danyfirex смог реализовать правильное вычисление хэш-суммы на чистом PowerShell, но, к сожалению, после проведённых тестов выяснилось, что сам PowerShell 5.1 считает его неправильно, поэтому я вынужден был использовать код другого разработчика с алгоритмом, реализованным на чистом C#. Крайне быстро! Функция огромная, поэтому просто оставлю просто ссылку на код.

Автоматизация установки бесплатного расширения для встроенного UWP-приложения Фотографии

Еще одна забавная, но крайне полезная функция даёт возможность открывать файлы формата .heic и .heif.

Расширение крайне полезное, так как все современные телефоны умеют делать фотографии в формате HEIC, но по умолчанию Windows 10 не умеет открывать такие файлы, предлагая купить расширение в Microsoft Store по цене в 0,99 $. Но мало кто знает, что есть скрытая страница от поиска этого же самого расширения, но предназначенного для OEM-производителей. Эту же страницу можно открыть вручную, выполнив через Win+R или через PowerShell: ms-windows-store://pdp/?ProductId=9n4wgh0z6vhq

Для скачивания установочного пакета на помощь приходит всеми известный сайт https://store.rg-adguard.net. Он позволяет, зная ID страницы, получать временные прямые ссылки на установочные пакеты. Значит, можно распарсить.

$API = "https://store.rg-adguard.net/api/GetFiles"# HEVC Video Extensions from Device Manufacturer$ProductURL = "https://www.microsoft.com/store/productId/9n4wgh0z6vhq"$Body = @{"type" = "url""url"  = $ProductURL"ring" = "Retail""lang" = "en-US"}$Raw = Invoke-RestMethod -Method Post -Uri $API -ContentType 'application/x-www-form-urlencoded' -Body $Body# Parsing the page$Raw | Select-String -Pattern '<tr style.*<a href=\"(?<url>.*)"\s.*>(?<text>.*)<\/a>' -AllMatches | ForEach-Object -Process {$_.Matches} | ForEach-Object -Process {$TempURL = $_.Groups[1].Value$Package = $_.Groups[2].Valueif ($Package -like "Microsoft.HEVCVideoExtension_*_x64__8wekyb3d8bbwe.appx"){[PSCustomObject]@{PackageName = $PackagePackageURL  = $TempURL}}}

Дальше уже дело техники сохранить и установить скачанный пакет.

Автопродление имен функций по введённым буквам, содержащимся в названии функции или её аргумента

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

Проблема в том, что до этого была лишь возможность вручную указывать функции и их аргументы а-ля .\Sophia.ps1 -Functions "DiagTrackService -Disable", "DiagnosticDataLevel -Minimal", UninstallUWPApps.

То есть ни о каком автопродлении речи и не шло: пользователю приходилось или запоминать имя функции и её аргумент, или копировать вручную данную комбинацию из пресет-файла. То ли дело сейчас!

Чтобы заработала сия шайтан-машина, пришлось прибегнуть к Register-ArgumentCompleter.

Весь код сосредоточен в отдельном файле, и не получится его поместить в текущий пресет-файл: файл необходимо вызывать с использованием dot sourcing. Одним словом, пришлось в ScriptBlock для argumentcompleter перебирать все возможные варианты конструкций вида "функция-аргумент" и просто "функция", если у последней нет собственного аргумента.

Теперь, чтобы вызвать, допустим, функцию по удалению UWP-приложений, можно ввести (после загрузки Functions.ps1) So<tab> -Fu<tab> uwp<tab>.

Крайне жутко выглядит, но стало гораздо удобнее.

Ну, а закончу рассказ на том, что даже сборка прикрепляемых архивов на странице релизов стала осуществляться с помощью конфига Github Actions. Как можно заметить, для создания архива под версию для PowerShell 7 приходится выкачивать две библиотеки с ресурсов Microsoft, так как загрузить файлы больше 25 МБ в репозиторий невозможно. Автоматизируй автоматизацию!

Итоги

Это были крайне плодотворные полгода. У нас такое ощущение, что мы прошли PowerShell на уровне "Ultra Violence". Ну, а что дальше? Параллельно я прорабатываю вариант, как реализовать, используя текущий паттерн взаимодействия пользователя со скриптом, настройку офлайновых образов WIM. Но главный приоритет для нас сейчас, конечно, разработка SophiApp.

Цель проекта показать, как, по нашему мнению, должен выглядеть, чувствоваться и каким функционалом обладать так называемый твикер для Windows 10. Идей просто огромное количество! Хотя у нас нет опыта в разработке и нас всего лишь двое, а весь код на SophiApp пишет в одиночку Дмитрий, возможно, летом уже появится первый рабочий билд. Но это уже совсем другая история.

Хочу выразить огромную благодарность также пользователям forum.ru-board westlife и iNNOKENTIY21: ребят, без вашей помощи и подсказок, всё было бы по-другому! А логотип нарисовала художница tea_head, за что ей тоже спасибо. Скрины, использованные в материале, взяты из мультфильма Коргот-варвар. Любите Windows 10, настраивайте её с умом и до новых встреч!

А если хотите прокачать себя, например получить навыки пентестера и зарабатывать на уязвимостях, или подтянуть знания алгоритмов и структур данных приходите учиться, будет сложно, но интересно!

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru