Add PowerShell as a Windows FMA (#46941)
This pull request adds support for PowerShell as a managed Windows app, including its metadata, versioning, install/uninstall scripts, and validation logic in the CI workflow. It also ensures that any pre-installed PowerShell 7 is removed from the test environment before validation. The most important changes are: **PowerShell app definition and metadata:** * Added a new entry for PowerShell in `ee/maintained-apps/inputs/winget/powershell.json` and included its metadata in `ee/maintained-apps/outputs/apps.json`, enabling PowerShell to be managed as a Windows app. [[1]](diffhunk://#diff-7e4899a379d327948f55b7e6ba4c9893501713edd16020b128b68343569fcbfbR1-R10) [[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R1985-R1991) * Created `ee/maintained-apps/outputs/powershell/windows.json` containing version information, SQL queries for existence and patch status, installer/uninstaller scripts, and download URL for PowerShell 7.6.2.0. **CI workflow enhancements for PowerShell:** * Updated `.github/workflows/test-fma-windows-pr-only.yml` to detect changes involving PowerShell, set the appropriate output flags, and conditionally execute PowerShell-specific steps. [[1]](diffhunk://#diff-51641fd1d2cc19348b81fd8310b62ad270ca5082ceddff2d49064e78f126a1eaR102) [[2]](diffhunk://#diff-51641fd1d2cc19348b81fd8310b62ad270ca5082ceddff2d49064e78f126a1eaR116) [[3]](diffhunk://#diff-51641fd1d2cc19348b81fd8310b62ad270ca5082ceddff2d49064e78f126a1eaR155-R162) * Added a workflow step to remove any pre-installed PowerShell 7 from the GitHub Actions runner, ensuring a clean environment for validation. * Ensured that validation and related steps use Windows PowerShell 5.1 (not pwsh) after uninstalling PowerShell 7, to avoid shell availability issues. These changes collectively enable PowerShell to be validated, installed, and uninstalled as part of the managed apps ecosystem and ensure reliable, repeatable CI validation. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added PowerShell as a supported application with full Windows integration, including installation and uninstall management capabilities. * Enhanced deployment scripts to support flexible input handling for app configurations. * Added PowerShell icon to the frontend UI for visual identification. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to filter apps.json to only include specified app slugs
|
||||
# Usage: filter-apps-json.sh <slugs_json_array> <output_file>
|
||||
# Usage: filter-apps-json.sh <slugs_json_array | slugs_json_file> <output_file>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -16,9 +16,20 @@ if ! command -v jq &> /dev/null; then
|
||||
fi
|
||||
|
||||
# Parse arguments
|
||||
SLUGS_JSON="$1"
|
||||
SLUGS_INPUT="$1"
|
||||
OUTPUT_FILE="$2"
|
||||
|
||||
# Accept the slugs as either a literal JSON array string or a path to a file containing
|
||||
# the JSON array. The file form avoids cross-shell quoting problems: Windows PowerShell
|
||||
# mangles embedded quotes when forwarding a JSON string as a native-command argument to
|
||||
# bash, corrupting the value before jq sees it. Callers passing a literal JSON string
|
||||
# (e.g. on macOS) are unaffected since that value is not a path to an existing file.
|
||||
if [ -n "$SLUGS_INPUT" ] && [ -f "$SLUGS_INPUT" ]; then
|
||||
SLUGS_JSON="$(cat "$SLUGS_INPUT")"
|
||||
else
|
||||
SLUGS_JSON="$SLUGS_INPUT"
|
||||
fi
|
||||
|
||||
if [ -z "$SLUGS_JSON" ] || [ "$SLUGS_JSON" == "[]" ] || [ "$SLUGS_JSON" == "null" ]; then
|
||||
echo "No slugs provided, creating empty apps.json"
|
||||
echo '{"version": 2, "apps": []}' > "$OUTPUT_FILE"
|
||||
|
||||
@@ -99,6 +99,7 @@ jobs:
|
||||
"has_7zip=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
"has_firefox=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
"has_nodejs=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
"has_powershell=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
exit 0
|
||||
}
|
||||
|
||||
@@ -112,6 +113,7 @@ jobs:
|
||||
"has_7zip=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
"has_firefox=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
"has_nodejs=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
"has_powershell=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
Write-Host "No windows apps changed, skipping Windows workflow"
|
||||
} else {
|
||||
"has_windows_apps=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
@@ -150,6 +152,14 @@ jobs:
|
||||
} else {
|
||||
"has_nodejs=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
}
|
||||
|
||||
# Check if powershell/windows is in the changed apps
|
||||
if ("powershell/windows" -in $windowsSlugs) {
|
||||
"has_powershell=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
Write-Host "PowerShell detected in changed apps"
|
||||
} else {
|
||||
"has_powershell=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
}
|
||||
}
|
||||
shell: pwsh
|
||||
|
||||
@@ -405,6 +415,79 @@ jobs:
|
||||
}
|
||||
shell: pwsh
|
||||
|
||||
- name: Remove pre-installed PowerShell
|
||||
if: steps.check-windows-apps.outputs.has_windows_apps == 'true' && steps.check-windows-apps.outputs.has_powershell == 'true'
|
||||
# NOTE: this step (and the verify step below) run under Windows PowerShell 5.1
|
||||
# (shell: powershell), NOT pwsh. We are about to uninstall PowerShell 7, so we
|
||||
# must not be executing inside pwsh.exe (it would be locked / unavailable).
|
||||
run: |
|
||||
Write-Host "Listing all installed packages containing 'PowerShell':"
|
||||
Get-Package | Where-Object { $_.Name -like "*PowerShell*" } | ForEach-Object {
|
||||
Write-Host " - $($_.Name) (Version: $($_.Version))"
|
||||
}
|
||||
|
||||
# PowerShell 7 installs via MSI and registers under "PowerShell 7-x64" /
|
||||
# "Microsoft Corporation". GitHub-hosted windows runners ship with it
|
||||
# pre-installed, which must be removed so the validator starts from a clean state.
|
||||
$uninstallPaths = @(
|
||||
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
|
||||
"HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
|
||||
)
|
||||
|
||||
$found = $false
|
||||
foreach ($path in $uninstallPaths) {
|
||||
$entries = Get-ItemProperty $path -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "PowerShell 7*" -and $_.Publisher -like "*Microsoft Corporation*" }
|
||||
foreach ($entry in $entries) {
|
||||
if (-not $entry) { continue }
|
||||
$found = $true
|
||||
Write-Host "Found PowerShell uninstall entry: $($entry.DisplayName) (Version: $($entry.DisplayVersion))"
|
||||
|
||||
$uninstallString = if ($entry.QuietUninstallString) {
|
||||
$entry.QuietUninstallString
|
||||
} elseif ($entry.UninstallString) {
|
||||
$entry.UninstallString
|
||||
} else {
|
||||
$null
|
||||
}
|
||||
|
||||
if ($uninstallString) {
|
||||
Write-Host "Found PowerShell uninstall path: $uninstallString"
|
||||
try {
|
||||
# PowerShell 7 uses an MSI uninstaller (MsiExec.exe /X{GUID} or /I{GUID})
|
||||
if ($uninstallString -match "/[XI]\{([A-F0-9\-]+)\}") {
|
||||
$guid = $matches[1]
|
||||
Write-Host "Uninstalling PowerShell MSI with GUID: $guid"
|
||||
Start-Process -FilePath "msiexec.exe" -ArgumentList "/X{$guid}", "/quiet", "/norestart" -Wait -NoNewWindow
|
||||
Write-Host "Successfully removed PowerShell via MSI uninstaller"
|
||||
} else {
|
||||
Write-Host "Could not parse uninstall string format: $uninstallString"
|
||||
}
|
||||
} catch {
|
||||
Write-Host "Failed to remove PowerShell: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Host "PowerShell uninstall string not found in registry entry"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $found) {
|
||||
Write-Host "PowerShell uninstall path not found in registry"
|
||||
}
|
||||
|
||||
# Force-remove leftover PowerShell 7 directory in case files remain after MSI removal
|
||||
$psDir = "C:\Program Files\PowerShell\7"
|
||||
if (Test-Path $psDir) {
|
||||
Write-Host "Removing leftover directory: $psDir"
|
||||
Remove-Item -Path $psDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
if (Test-Path $psDir) {
|
||||
Write-Host "WARNING: Failed to fully remove $psDir"
|
||||
} else {
|
||||
Write-Host "Removed $psDir"
|
||||
}
|
||||
}
|
||||
shell: powershell
|
||||
|
||||
- name: Filter apps.json and verify changed apps
|
||||
if: steps.check-windows-apps.outputs.has_windows_apps == 'true'
|
||||
run: |
|
||||
@@ -415,16 +498,33 @@ jobs:
|
||||
# Filter changed apps to only include windows platform
|
||||
$changedAppsJson = '${{ steps.detect-changed.outputs.CHANGED_APPS }}'
|
||||
$windowsSlugs = ($changedAppsJson | ConvertFrom-Json | Where-Object { $_ -like "*/windows" })
|
||||
$windowsSlugsJson = ($windowsSlugs | ConvertTo-Json -Compress)
|
||||
|
||||
# Build a JSON array of the windows slugs. Construct it explicitly so a single
|
||||
# slug still serializes as an array (Windows PowerShell unwraps single-element
|
||||
# arrays via ConvertTo-Json), and write it to a BOM-free file. We then pass the
|
||||
# file PATH -- not the JSON string -- to the bash script: forwarding a quoted
|
||||
# JSON string across the PowerShell -> bash argument boundary mangles the
|
||||
# embedded quotes under Windows PowerShell 5.1, which breaks jq --argjson.
|
||||
$slugsArray = @($windowsSlugs)
|
||||
$windowsSlugsJson = "[" + (($slugsArray | ForEach-Object { $_ | ConvertTo-Json -Compress }) -join ",") + "]"
|
||||
Write-Host "Filtering apps.json for slugs: $windowsSlugsJson"
|
||||
|
||||
$windowsSlugsFile = Join-Path $env:TEMP "windows-slugs-$(New-Guid).json"
|
||||
Set-Content -Path $windowsSlugsFile -Value $windowsSlugsJson -Encoding ascii -NoNewline
|
||||
# Use forward slashes so Git Bash reads the path reliably (it reads this arg as a file).
|
||||
$windowsSlugsFileForBash = $windowsSlugsFile -replace '\\', '/'
|
||||
|
||||
# Backup original apps.json
|
||||
Copy-Item -Path "ee\maintained-apps\outputs\apps.json" -Destination "ee\maintained-apps\outputs\apps.json.backup"
|
||||
|
||||
# Create filtered apps.json
|
||||
# Use a fixed path for the temp file to avoid issues with bash
|
||||
$filteredAppsJson = Join-Path $env:TEMP "filtered-apps-$(New-Guid).json"
|
||||
bash .github/scripts/filter-apps-json.sh "$windowsSlugsJson" "$filteredAppsJson"
|
||||
bash .github/scripts/filter-apps-json.sh "$windowsSlugsFileForBash" "$filteredAppsJson"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Error: filter-apps-json.sh failed with exit code $LASTEXITCODE"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Verify the filtered file was created
|
||||
if (-not (Test-Path $filteredAppsJson)) {
|
||||
@@ -441,4 +541,6 @@ jobs:
|
||||
|
||||
# Restore original apps.json
|
||||
Move-Item -Path "ee\maintained-apps\outputs\apps.json.backup" -Destination "ee\maintained-apps\outputs\apps.json" -Force
|
||||
shell: pwsh
|
||||
# Use Windows PowerShell 5.1 (not pwsh): when validating the PowerShell FMA we
|
||||
# uninstall PowerShell 7 in the step above, so pwsh.exe may not be available here.
|
||||
shell: powershell
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "PowerShell",
|
||||
"slug": "powershell/windows",
|
||||
"package_identifier": "Microsoft.PowerShell",
|
||||
"unique_identifier": "PowerShell 7-x64",
|
||||
"installer_arch": "x64",
|
||||
"installer_type": "msi",
|
||||
"installer_scope": "machine",
|
||||
"default_categories": ["Developer tools"]
|
||||
}
|
||||
@@ -1982,6 +1982,13 @@
|
||||
"unique_identifier": "Microsoft Power BI Desktop (x64)",
|
||||
"description": "Power BI is an interactive data visualization platform with a primary focus on business intelligence (BI)."
|
||||
},
|
||||
{
|
||||
"name": "PowerShell",
|
||||
"slug": "powershell/windows",
|
||||
"platform": "windows",
|
||||
"unique_identifier": "PowerShell 7-x64",
|
||||
"description": "PowerShell is a cross-platform task automation solution made up of a command-line shell, a scripting language, and a configuration management framework."
|
||||
},
|
||||
{
|
||||
"name": "PowerToys",
|
||||
"slug": "powertoys/windows",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "7.6.2.0",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM programs WHERE name = 'PowerShell 7-x64' AND publisher = 'Microsoft Corporation';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'PowerShell 7-x64' AND publisher = 'Microsoft Corporation' AND version_compare(version, '7.6.2.0') < 0);"
|
||||
},
|
||||
"installer_url": "https://github.com/PowerShell/PowerShell/releases/download/v7.6.2/PowerShell-7.6.2-win-x64.msi",
|
||||
"install_script_ref": "8959087b",
|
||||
"uninstall_script_ref": "dccec067",
|
||||
"sha256": "096a6dbb5bb330c5e14559ff1a7081bd274c07c07e2545755b93a93417e32629",
|
||||
"default_categories": [
|
||||
"Developer tools"
|
||||
]
|
||||
}
|
||||
],
|
||||
"refs": {
|
||||
"8959087b": "$logFile = \"${env:TEMP}/fleet-install-software.log\"\n\ntry {\n\n$installProcess = Start-Process msiexec.exe `\n -ArgumentList \"/quiet /norestart /lv ${logFile} /i `\"${env:INSTALLER_PATH}`\"\" `\n -PassThru -Verb RunAs -Wait\n\nGet-Content $logFile -Tail 500\n\nExit $installProcess.ExitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n",
|
||||
"dccec067": "$product_code = '{E5C8C749-88E5-48D7-9D16-4C41ED869462}'\n$timeoutSeconds = 300 # 5 minute timeout\n\n# Fleet uninstalls app using product code that's extracted on upload\n$process = Start-Process msiexec -ArgumentList @(\"/quiet\", \"/x\", $product_code, \"/norestart\") -PassThru\n\n# Wait for process with timeout\n$completed = $process.WaitForExit($timeoutSeconds * 1000)\n\nif (-not $completed) {\n Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue\n Exit 1603 # ERROR_UNINSTALL_FAILURE\n}\n\n# Check exit code and output result\nif ($process.ExitCode -eq 0) {\n Write-Output \"Exit 0\"\n Exit 0\n} else {\n Write-Output \"Exit $($process.ExitCode)\"\n Exit $process.ExitCode\n}\n"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -55,6 +55,7 @@ import PlantronicsHub from "./PlantronicsHub";
|
||||
import PowerAutomate from "./PowerAutomate";
|
||||
import PowerBi from "./PowerBi";
|
||||
import Plugdata from "./Plugdata";
|
||||
import Powershell from "./Powershell";
|
||||
import Powertoys from "./Powertoys";
|
||||
import Prisma from "./Prisma";
|
||||
import Proxifier from "./Proxifier";
|
||||
@@ -516,6 +517,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = {
|
||||
postman: Postman,
|
||||
"power automate": PowerAutomate,
|
||||
"power bi": PowerBi,
|
||||
powershell: Powershell,
|
||||
powertoys: Powertoys,
|
||||
prisma: Prisma,
|
||||
privileges: Privileges,
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Reference in New Issue
Block a user