[CmdNotFound]Improve installation workflow (#30727)

* [CmdNotFound]Improve installation workflow

* Fix typo

* Fix XAML styling

* Update src/settings-ui/Settings.UI/SettingsXAML/Views/CmdNotFoundPage.xaml

Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>

* Update src/settings-ui/Settings.UI/SettingsXAML/Views/CmdNotFoundPage.xaml

Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>

* Update src/settings-ui/Settings.UI/SettingsXAML/Views/CmdNotFoundPage.xaml

Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>

* Update src/settings-ui/Settings.UI/SettingsXAML/Views/CmdNotFoundPage.xaml

Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>

* Update src/settings-ui/Settings.UI/SettingsXAML/Views/CmdNotFoundPage.xaml

Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>

* Update src/settings-ui/Settings.UI/SettingsXAML/Views/CmdNotFoundPage.xaml

Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>

* Don't create window for checking requirements

* Hide install/uninstall buttons for CmdNotFound module

* Also install winget before Powershell 7

* Better detect processor architecture

* Fix spellcheck

* [CMDNotFound] UX improvements (#30740)

* UI improvements

* Updated label

* Update CmdNotFoundPage.xaml

* Update CmdNotFoundPage.xaml

* Update CmdNotFoundPage.xaml

* Better version detection

* Add some logging

* Add missing package install that's a prerequisite for winget

* Remove unused AllExperiments include

* Fix Logger library include

* Update PATH Environment variable after installing PS

* Fix OOBE UI spacing

* Use Invoke-WebRequest to get the deps and then install them from local path

* Spellcheck

* TEMP -> TMP

* User path is supposed to come after machine path

---------

Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Stefan Markovic <stefan@janeasystems.com>
This commit is contained in:
Jaime Bernardo 2024-01-05 09:26:49 +00:00 committed by GitHub
parent 5f2d8216ad
commit a7907ff63a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 456 additions and 45 deletions

View File

@ -745,6 +745,7 @@ Kybd
languagesjson
lastcodeanalysissucceeded
Lastdevice
LASTEXITCODE
LAYOUTRTL
LCIDTo
lcl
@ -917,6 +918,7 @@ MSIFASTINSTALL
MSIHANDLE
msiquery
MSIRESTARTMANAGERCONTROL
msixbundle
MSIXCA
MSLLHOOKSTRUCT
Mso

View File

@ -54,6 +54,9 @@
</RegistryKey>
<File Id="CommandNotFound_Scripts_EnableModule.ps1" Source="$(var.SettingsV2AssetsFilesPath)\Scripts\EnableModule.ps1" />
<File Id="CommandNotFound_Scripts_DisableModule.ps1" Source="$(var.SettingsV2AssetsFilesPath)\Scripts\DisableModule.ps1" />
<File Id="CommandNotFound_Scripts_CheckCmdNotFoundRequirements.ps1" Source="$(var.SettingsV2AssetsFilesPath)\Scripts\CheckCmdNotFoundRequirements.ps1" />
<File Id="CommandNotFound_Scripts_InstallWinGetClientModule.ps1" Source="$(var.SettingsV2AssetsFilesPath)\Scripts\InstallWinGetClientModule.ps1" />
<File Id="CommandNotFound_Scripts_InstallPowerShell7.ps1" Source="$(var.SettingsV2AssetsFilesPath)\Scripts\InstallPowerShell7.ps1" />
</Component>
</DirectoryRef>

View File

@ -0,0 +1,41 @@
Write-Host $PSVersionTable
if ($PSVersionTable.PSVersion -ge 7.4)
{
Write-Host "PowerShell 7.4 or greater detected."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
else
{
Write-Host "PowerShell 7.4 or greater not detected. Installation instructions can be found on https://learn.microsoft.com/powershell/scripting/install/installing-powershell-on-windows `r`n"
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
if (Get-Module -ListAvailable -Name Microsoft.WinGet.Client)
{
Write-Host "WinGet Client module detected."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
else {
Write-Host "WinGet Client module not detected. Installation instructions can be found on https://www.powershellgallery.com/packages/Microsoft.WinGet.Client `r`n"
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
if (!(Test-Path $PROFILE))
{
Write-Host "Profile file $PROFILE not found".
New-Item -Path $PROFILE -ItemType File
Write-Host "Created profile file $PROFILE".
}
$profileContent = Get-Content -Path $PROFILE -Raw
if ((-not [string]::IsNullOrEmpty($profileContent)) -and ($profileContent.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756")))
{
Write-Host "Command Not Found module is registered in the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
else
{
Write-Host "Command Not Found module is not registered in the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}

View File

@ -30,6 +30,8 @@ if($atLeastOneInstanceFound)
{
Set-Content -Path $PROFILE -Value $newContent
Write-Host "Removed the Command Not Found reference from the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
} else {
Write-Host "No instance of Command Not Found was found in the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}

View File

@ -28,6 +28,7 @@ $profileContent = Get-Content -Path $PROFILE -Raw
if ((-not [string]::IsNullOrEmpty($profileContent)) -and ($profileContent.Contains("34de4b3d-13a8-4540-b76d-b9e8d3851756")))
{
Write-Host "Module is already registered in the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
else
{
@ -35,4 +36,5 @@ else
Add-Content -Path $PROFILE -Value "`r`nImport-Module `"$scriptPath\WinGetCommandNotFound.psd1`""
Add-Content -Path $PROFILE -Value "#34de4b3d-13a8-4540-b76d-b9e8d3851756"
Write-Host "Module was successfully registered in the profile file."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}

View File

@ -0,0 +1,81 @@
if ((Get-AppxPackage microsoft.DesktopAppInstaller).Version -ge [System.Version]"1.21")
{
Write-Host "Detected winget. Will try to install PowerShell."
}
else
{
Write-Host "WinGet not detected. Will try to install."
# To speed up Invoke-WebRequest. Older versions are very slow when printing the progress.
$ProgressPreference = 'SilentlyContinue'
$cpuArchitecture="x64"
$detectedArchitecture=""
if ($env:PROCESSOR_ARCHITEW6432 -eq $null) {
$detectedArchitecture=$env:PROCESSOR_ARCHITECTURE
} else {
$detectedArchitecture=$env:PROCESSOR_ARCHITEW6432
}
Write-Host "Detected the CPU architecture:$detectedArchitecture"
if ($detectedArchitecture -ne "AMD64")
{
Write-Host "Mismatch with AMD64, setting it to arm64, since that's where we're likely running."
$cpuArchitecture="arm64"
}
if((Get-AppxPackage Microsoft.VCLibs.140.00).Version -ge [System.Version]"14.0.30704")
{
Write-Host "Detected Microsoft.VCLibs.140.00."
}
else
{
Write-Host "Microsoft.VCLibs.140.00 not detected. Will try to install."
Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.$cpuArchitecture.14.00.Desktop.appx -OutFile "$Env:TMP\Microsoft.VCLibs.14.00.appx"
Add-AppxPackage -Path "$Env:TMP\Microsoft.VCLibs.14.00.appx"
Remove-Item -Path "$Env:TMP\Microsoft.VCLibs.14.00.appx" -Force
}
if((Get-AppxPackage Microsoft.VCLibs.140.00.UWPDesktop).Version -ge [System.Version]"14.0.30704")
{
Write-Host "Detected Microsoft.VCLibs.140.00.UWPDesktop"
}
else
{
Write-Host "Microsoft.VCLibs.140.00.UWPDesktop not detected. Will try to install."
Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.$cpuArchitecture.14.00.Desktop.appx -OutFile "$Env:TMP\Microsoft.VCLibs.14.00.Desktop.appx"
Add-AppxPackage -Path "$Env:TMP\Microsoft.VCLibs.14.00.Desktop.appx"
Remove-Item -Path "$Env:TMP\Microsoft.VCLibs.14.00.Desktop.appx" -Force
}
if (Get-AppxPackage Microsoft.UI.Xaml.2.7)
{
Write-Host "Detected Microsoft.UI.Xaml.2.7"
}
else
{
Write-Host "Microsoft.UI.Xaml.2.7 not detected. Will try to install."
Write-Host "Downloading to $Env:TMP\microsoft.ui.xaml.2.7.3.zip"
Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.7.3 -OutFile "$Env:TMP\microsoft.ui.xaml.2.7.3.zip"
Write-Host "Extracting $Env:TMP\microsoft.ui.xaml.2.7.3.zip"
Expand-Archive "$Env:TMP\microsoft.ui.xaml.2.7.3.zip" -DestinationPath "$Env:TMP\microsoft.ui.xaml.2.7.3"
Write-Host "Installing $Env:TMP\microsoft.ui.xaml.2.7.3\tools\AppX\$cpuArchitecture\Release\Microsoft.UI.Xaml.2.7.appx"
Add-AppxPackage "$Env:TMP\microsoft.ui.xaml.2.7.3\tools\AppX\$cpuArchitecture\Release\Microsoft.UI.Xaml.2.7.appx"
Remove-Item -Path "$Env:TMP\microsoft.ui.xaml.2.7.3" -Recurse -Force
Remove-Item -Path "$Env:TMP\microsoft.ui.xaml.2.7.3.zip" -Force
}
Write-Host "Getting winget to the latest stable"
Invoke-WebRequest -Uri https://aka.ms/getwinget -OutFile "$Env:TMP\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
Add-AppxPackage -Path "$Env:TMP\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
Remove-Item -Path "$Env:TMP\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" -Force
#winget is not visible right away, so reload the PATH variable.
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
}
winget install Microsoft.PowerShell --source winget
if ($LASTEXITCODE -eq 0)
{
Write-Host "Powershell 7 successfully installed."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
else
{
Write-Host "Powershell 7 was not installed."
}

View File

@ -0,0 +1,16 @@
if (Get-Module -ListAvailable -Name Microsoft.WinGet.Client)
{
Write-Host "WinGet Client module detected."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
else {
Install-Module -Name Microsoft.WinGet.Client
if (Get-Module -ListAvailable -Name Microsoft.WinGet.Client)
{
Write-Host "WinGet Client module detected."
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
} else {
Write-Host "WinGet Client module not detected. Installation instructions can be found on https://www.powershellgallery.com/packages/Microsoft.WinGet.Client `r`n"
# This message will be compared against in Command Not Found Settings page code behind. Take care when changing it.
}
}

View File

@ -119,19 +119,28 @@
<NoWarn>VSTHRD002;VSTHRD110;VSTHRD100;VSTHRD200;VSTHRD101</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Update="Assets\Settings\icon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Update="Assets\Settings\icon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Update="Assets\Settings\Scripts\EnableModule.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\Settings\Scripts\DisableModule.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Update="Assets\Settings\Scripts\CheckCmdNotFoundRequirements.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\Settings\Scripts\InstallWinGetClientModule.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\Settings\Scripts\InstallPowerShell7.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\Settings\Scripts\EnableModule.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\Settings\Scripts\DisableModule.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -12,10 +12,10 @@
<controls:OOBEPageControl x:Uid="Oobe_CmdNotFound" HeroImage="ms-appx:///Assets/Settings/Modules/OOBE/CmdNotFound.png">
<controls:OOBEPageControl.PageContent>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock x:Uid="Oobe_HowToUse" Style="{ThemeResource OobeSubtitleStyle}" />
<controls:ShortcutWithTextLabelControl x:Name="HotkeyControl" x:Uid="Oobe_CmdNotFound_HowToUse" />
<toolkitcontrols:MarkdownTextBlock x:Uid="Oobe_CmdNotFound_HowToUse" Background="Transparent" />
<StackPanel
Margin="0,24,0,0"

View File

@ -6,10 +6,17 @@
xmlns:custom="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:toolkitConverters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<Page.Resources>
<toolkitConverters:BoolToVisibilityConverter
x:Key="BoolToInvertedVisibilityConverter"
FalseValue="Visible"
TrueValue="Collapsed" />
<toolkitConverters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</Page.Resources>
<custom:SettingsPageControl x:Uid="CmdNotFound" ModuleImageSource="ms-appx:///Assets/Settings/Modules/CmdNotFound.png">
<custom:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
@ -20,25 +27,115 @@
IsTabStop="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabledGpoConfigured}"
Severity="Informational" />
<controls:SettingsCard
<controls:SettingsExpander
x:Uid="CmdNotFound_Enable"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/FluentIcons/FluentIconsCmdNotFound.png}"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabledGpoConfigured, Converter={StaticResource BoolNegationConverter}}">
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
x:Uid="CmdNotFound_InstallButton"
Command="{x:Bind ViewModel.InstallModuleEventHandler}"
Style="{StaticResource AccentButtonStyle}" />
<HyperlinkButton x:Uid="CmdNotFound_UninstallButton" Command="{x:Bind ViewModel.UninstallModuleEventHandler}" />
</StackPanel>
<controls:SettingsCard.Description>
<StackPanel Orientation="Vertical">
<TextBlock x:Uid="CmdNotFound_Enable_DescriptionText" />
<HyperlinkButton x:Uid="CmdNotFound_CheckCompatibility" Command="{x:Bind ViewModel.CheckPowershellVersionEventHandler}" />
</StackPanel>
</controls:SettingsCard.Description>
</controls:SettingsCard>
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabledGpoConfigured, Converter={StaticResource BoolNegationConverter}}"
IsExpanded="True">
<controls:SwitchPresenter TargetType="x:Boolean" Value="{x:Bind ViewModel.IsCommandNotFoundModuleInstalled, Mode=OneWay}">
<controls:Case Value="True">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
x:Uid="InstalledLabel"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<FontIcon
Margin="0,0,4,0"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
Foreground="{ThemeResource SystemFillColorSuccessBrush}"
Glyph="&#xEC61;" />
<HyperlinkButton x:Uid="CmdNotFound_UninstallButton" Command="{x:Bind ViewModel.UninstallModuleEventHandler}" />
</StackPanel>
</controls:Case>
<controls:Case Value="False">
<Button
x:Uid="CmdNotFound_InstallButton"
Command="{x:Bind ViewModel.InstallModuleEventHandler}"
Style="{StaticResource AccentButtonStyle}" />
</controls:Case>
</controls:SwitchPresenter>
<controls:SettingsExpander.ItemsHeader>
<InfoBar
x:Uid="CmdNotFound_RequirementsBar"
BorderThickness="0"
CornerRadius="0"
IsClosable="False"
IsOpen="True">
<InfoBar.ActionButton>
<HyperlinkButton x:Uid="CmdNotFound_CheckCompatibility" Command="{x:Bind ViewModel.CheckRequirementsEventHandler}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="CmdNotFound_CheckCompatibilityTooltip" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
</HyperlinkButton>
</InfoBar.ActionButton>
</InfoBar>
</controls:SettingsExpander.ItemsHeader>
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="CmdNotFound_PowerShellDetection">
<controls:SwitchPresenter TargetType="x:Boolean" Value="{x:Bind ViewModel.IsPowerShell7Detected, Mode=OneWay}">
<controls:Case Value="True">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
x:Uid="DetectedLabel"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<FontIcon
AutomationProperties.AccessibilityView="Raw"
Foreground="{ThemeResource SystemFillColorSuccessBrush}"
Glyph="&#xEC61;" />
</StackPanel>
</controls:Case>
<controls:Case Value="False">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
x:Uid="NotDetectedLabel"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<FontIcon
Margin="0,0,4,0"
AutomationProperties.AccessibilityView="Raw"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
Glyph="&#xEB90;" />
<Button x:Uid="CmdNotFound_InstallButton" Command="{x:Bind ViewModel.InstallPowerShell7EventHandler}" />
</StackPanel>
</controls:Case>
</controls:SwitchPresenter>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="CmdNotFound_WinGetClientDetection">
<controls:SwitchPresenter TargetType="x:Boolean" Value="{x:Bind ViewModel.IsWinGetClientModuleDetected, Mode=OneWay}">
<controls:Case Value="True">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
x:Uid="DetectedLabel"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<FontIcon
AutomationProperties.AccessibilityView="Raw"
Foreground="{ThemeResource SystemFillColorSuccessBrush}"
Glyph="&#xEC61;" />
</StackPanel>
</controls:Case>
<controls:Case Value="False">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
x:Uid="NotDetectedLabel"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<FontIcon
Margin="0,0,4,0"
AutomationProperties.AccessibilityView="Raw"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
Glyph="&#xEB90;" />
<Button x:Uid="CmdNotFound_InstallButton" Command="{x:Bind ViewModel.InstallWinGetClientModuleEventHandler}" />
</StackPanel>
</controls:Case>
</controls:SwitchPresenter>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<TextBlock
x:Uid="CmdNotFound_ModuleInstallationLogs"
Margin="0,12,0,4"

View File

@ -3676,12 +3676,30 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="CmdNotFound.ModuleTitle" xml:space="preserve">
<value>Command Not Found</value>
<comment>"Command Not Found" is a product name</comment>
</data>
<data name="InstalledLabel.Text" xml:space="preserve">
<value>Installed</value>
</data>
<data name="DetectedLabel.Text" xml:space="preserve">
<value>Detected</value>
</data>
<data name="NotDetectedLabel.Text" xml:space="preserve">
<value>Not detected</value>
</data>
<data name="CmdNotFound_RequirementsBar.Title" xml:space="preserve">
<value>The following components are required</value>
</data>
<data name="CmdNotFound_PowerShellDetection.Header" xml:space="preserve">
<value>PowerShell 7.4 or greater</value>
</data>
<data name="CmdNotFound_WinGetClientDetection.Header" xml:space="preserve">
<value>WinGet Client PowerShell module</value>
</data>
<data name="CmdNotFound_Enable.Header" xml:space="preserve">
<value>Command Not Found</value>
<comment>"Command Not Found" is a product name</comment>
</data>
<data name="CmdNotFound_Enable_DescriptionText.Text" xml:space="preserve">
<data name="CmdNotFound_Enable.Description" xml:space="preserve">
<value>Add this module to the PowerShell 7 profile script so that it is enabled with every new session</value>
</data>
<data name="CmdNotFound_ModuleInstallationLogs.Text" xml:space="preserve">
@ -3691,7 +3709,10 @@ Activate by holding the key for the character you want to add an accent to, then
<value>PowerShell 7 is required to use this module</value>
</data>
<data name="CmdNotFound_CheckCompatibility.Content" xml:space="preserve">
<value>Check if your PowerShell configuration is compatible</value>
<value>Refresh</value>
</data>
<data name="CmdNotFound_CheckCompatibilityTooltip.Text" xml:space="preserve">
<value>Check if your PowerShell configuration is compatible and configured correctly</value>
</data>
<data name="CmdNotFound_InstallButton.Content" xml:space="preserve">
<value>Install</value>

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.IO;
using System.Reflection;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
@ -16,7 +17,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class CmdNotFoundViewModel : Observable
{
public ButtonClickCommand CheckPowershellVersionEventHandler => new ButtonClickCommand(CheckPowershellVersion);
public ButtonClickCommand CheckRequirementsEventHandler => new ButtonClickCommand(CheckCommandNotFoundRequirements);
public ButtonClickCommand InstallPowerShell7EventHandler => new ButtonClickCommand(InstallPowerShell7);
public ButtonClickCommand InstallWinGetClientModuleEventHandler => new ButtonClickCommand(InstallWinGetClientModule);
public ButtonClickCommand InstallModuleEventHandler => new ButtonClickCommand(InstallModule);
@ -49,6 +54,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Get the enabled state from GPO.
_enabledStateIsGPOConfigured = true;
}
CheckCommandNotFoundRequirements();
}
private string _commandOutputLog;
@ -66,20 +73,66 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
private bool _isPowerShell7Detected;
public bool IsPowerShell7Detected
{
get => _isPowerShell7Detected;
set
{
if (_isPowerShell7Detected != value)
{
_isPowerShell7Detected = value;
OnPropertyChanged(nameof(IsPowerShell7Detected));
}
}
}
private bool _isWinGetClientModuleDetected;
public bool IsWinGetClientModuleDetected
{
get => _isWinGetClientModuleDetected;
set
{
if (_isWinGetClientModuleDetected != value)
{
_isWinGetClientModuleDetected = value;
OnPropertyChanged(nameof(IsWinGetClientModuleDetected));
}
}
}
private bool _isCommandNotFoundModuleInstalled;
public bool IsCommandNotFoundModuleInstalled
{
get => _isCommandNotFoundModuleInstalled;
set
{
if (_isCommandNotFoundModuleInstalled != value)
{
_isCommandNotFoundModuleInstalled = value;
OnPropertyChanged(nameof(IsCommandNotFoundModuleInstalled));
}
}
}
public bool IsEnabledGpoConfigured
{
get => _enabledStateIsGPOConfigured;
}
public void RunPowerShellScript(string powershellArguments)
public string RunPowerShellScript(string powershellExecutable, string powershellArguments, bool hidePowerShellWindow = false)
{
string outputLog = string.Empty;
try
{
var startInfo = new ProcessStartInfo()
{
FileName = "pwsh.exe",
FileName = powershellExecutable,
Arguments = powershellArguments,
CreateNoWindow = hidePowerShellWindow,
UseShellExecute = false,
RedirectStandardOutput = true,
};
@ -96,28 +149,112 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
CommandOutputLog = outputLog;
return outputLog;
}
public void CheckPowershellVersion()
public void CheckCommandNotFoundRequirements()
{
var arguments = $"-NoProfile -NonInteractive -Command $PSVersionTable";
RunPowerShellScript(arguments);
var ps1File = AssemblyDirectory + "\\Assets\\Settings\\Scripts\\CheckCmdNotFoundRequirements.ps1";
var arguments = $"-NoProfile -NonInteractive -ExecutionPolicy Unrestricted -File \"{ps1File}\"";
var result = RunPowerShellScript("pwsh.exe", arguments, true);
if (result.Contains("PowerShell 7.4 or greater detected."))
{
IsPowerShell7Detected = true;
}
else if (result.Contains("PowerShell 7.4 or greater not detected."))
{
IsPowerShell7Detected = false;
}
else if (result.Contains("pwsh.exe"))
{
// Likely an error saying there was an error starting pwsh.exe, so we can assume Powershell 7 was not detected.
CommandOutputLog += "PowerShell 7.4 or greater not detected. Installation instructions can be found on https://learn.microsoft.com/powershell/scripting/install/installing-powershell-on-windows \r\n";
IsPowerShell7Detected = false;
}
if (result.Contains("WinGet Client module detected."))
{
IsWinGetClientModuleDetected = true;
}
else if (result.Contains("WinGet Client module not detected."))
{
IsWinGetClientModuleDetected = false;
}
if (result.Contains("Command Not Found module is registered in the profile file."))
{
IsCommandNotFoundModuleInstalled = true;
}
else if (result.Contains("Command Not Found module is not registered in the profile file."))
{
IsCommandNotFoundModuleInstalled = false;
}
Logger.LogInfo(result);
}
public void InstallPowerShell7()
{
var ps1File = AssemblyDirectory + "\\Assets\\Settings\\Scripts\\InstallPowerShell7.ps1";
var arguments = $"-NoProfile -ExecutionPolicy Unrestricted -File \"{ps1File}\"";
var result = RunPowerShellScript("powershell.exe", arguments);
if (result.Contains("Powershell 7 successfully installed."))
{
IsPowerShell7Detected = true;
}
Logger.LogInfo(result);
// Update PATH environment variable to get pwsh.exe on further calls.
Environment.SetEnvironmentVariable("PATH", (Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) ?? string.Empty) + ";" + (Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? string.Empty), EnvironmentVariableTarget.Process);
}
public void InstallWinGetClientModule()
{
var ps1File = AssemblyDirectory + "\\Assets\\Settings\\Scripts\\InstallWinGetClientModule.ps1";
var arguments = $"-NoProfile -ExecutionPolicy Unrestricted -File \"{ps1File}\"";
var result = RunPowerShellScript("pwsh.exe", arguments);
if (result.Contains("WinGet Client module detected."))
{
IsWinGetClientModuleDetected = true;
}
else if (result.Contains("WinGet Client module not detected."))
{
IsWinGetClientModuleDetected = false;
}
Logger.LogInfo(result);
}
public void InstallModule()
{
var ps1File = AssemblyDirectory + "\\Assets\\Settings\\Scripts\\EnableModule.ps1";
var arguments = $"-NoProfile -ExecutionPolicy Unrestricted -File \"{ps1File}\" -scriptPath \"{AssemblyDirectory}\\..\"";
RunPowerShellScript(arguments);
PowerToysTelemetry.Log.WriteEvent(new CmdNotFoundInstallEvent());
var result = RunPowerShellScript("pwsh.exe", arguments);
if (result.Contains("Module is already registered in the profile file.") || result.Contains("Module was successfully registered in the profile file."))
{
IsCommandNotFoundModuleInstalled = true;
PowerToysTelemetry.Log.WriteEvent(new CmdNotFoundInstallEvent());
}
Logger.LogInfo(result);
}
public void UninstallModule()
{
var ps1File = AssemblyDirectory + "\\Assets\\Settings\\Scripts\\DisableModule.ps1";
var arguments = $"-NoProfile -ExecutionPolicy Unrestricted -File \"{ps1File}\"";
RunPowerShellScript(arguments);
PowerToysTelemetry.Log.WriteEvent(new CmdNotFoundUninstallEvent());
var result = RunPowerShellScript("pwsh.exe", arguments);
if (result.Contains("Removed the Command Not Found reference from the profile file.") || result.Contains("No instance of Command Not Found was found in the profile file."))
{
IsCommandNotFoundModuleInstalled = false;
PowerToysTelemetry.Log.WriteEvent(new CmdNotFoundUninstallEvent());
}
Logger.LogInfo(result);
}
}
}
}