**Related issue:** Resolves #50567 Adds JetBrains ReSharper as a Windows Fleet-maintained app (winget `JetBrains.ReSharper`, version `2026.2.0.2`). ReSharper is a Visual Studio extension rather than a standalone app, so it does not follow the pattern of the other JetBrains FMAs (Rider, PhpStorm, DataGrip, etc.), which are plain NSIS installers that take `/S`. Reviewers should read the risks below before approving — a couple of things can only be confirmed from a validator run. ## What's here - `ee/maintained-apps/inputs/winget/resharper.json` - `ee/maintained-apps/inputs/winget/scripts/resharper_install.ps1` / `resharper_uninstall.ps1` - Generated `ee/maintained-apps/outputs/resharper/windows.json` + `apps.json` entry - Icon (`Resharper.tsx`, website PNG, alphabetical `index.ts` entries), generated from JetBrains' own brand asset ## Decisions that differ from the other JetBrains FMAs **`use_display_version_for_patch` is omitted.** Every other JetBrains winget input sets it, but the ReSharper manifest has no `AppsAndFeaturesEntries`, so the ingester hard-errors with `use_display_version_for_patch is set but no DisplayVersion found in winget manifest`. The patch policy therefore compares against winget's `2026.2.0.2`. **Custom `exists_query` instead of `fuzzy_match_name`.** The prefix is loose enough to match a possible per-VS-instance suffix, and excludes the separate ReSharper C++ and ReSharper SDK products: ```sql SELECT 1 FROM programs WHERE name LIKE 'JetBrains ReSharper%' AND name NOT LIKE 'JetBrains ReSharper C++%' AND name NOT LIKE 'JetBrains ReSharper SDK%' AND publisher = 'JetBrains s.r.o.'; ``` **Install script detects Visual Studio.** It builds `/VsVersion` from the instances `vswhere` reports and runs the installer with `/Silent=True /PerMachine=True /SkipEtwService=True`: - `/PerMachine=True` — the installer otherwise targets `%LocalAppData%`, which under Fleet's SYSTEM context would land in the SYSTEM profile instead of the developer's. The path is not configurable ([RSRP-428991](https://youtrack.jetbrains.com/issue/RSRP-428991)). - `/SkipEtwService=True` — JetBrains documents that `EtwHostService.msi` always raises a UAC prompt, so a fully silent install of every component is not possible ([SUPPORT-A-3189](https://youtrack.jetbrains.com/articles/SUPPORT-A-3189)). - It then waits for the uninstall registry entry (what osquery reads), because the web bootstrapper can outlive its own exit code, and logs the resulting ARP entries. **Uninstall removes every matching entry**, since ReSharper registers one per Visual Studio instance, using the defensive `UninstallString` parser and appending `/Silent=True` rather than the NSIS `/S`. Switches come from [JetBrains' silent install/uninstall article](https://resharper-support.jetbrains.com/hc/en-us/articles/207241485-How-to-use-silent-install-and-silent-uninstall-of-ReSharper-via-Command-Line), not guesswork. ## `unique_identifier` is provisional `program_publisher` is verified — `JetBrains s.r.o.` is hard-coded next to the ARP value names (`DisplayName`, `DisplayVersion`, `UninstallString`, `Publisher`) in `JetBrains.Platform.Installer.exe`, extracted from the installer. The **DisplayName is not verifiable offline.** The winget URL is a two-stage web bootstrapper: the 69 MB `.web.exe` contains `JetBrains.Platform.Installer.Bootstrap.exe`, which downloads the JetBrains dotUltimate installer, which downloads the product packages. The ARP entry is written by that downloaded stage under `Software\Microsoft\Windows\CurrentVersion\Uninstall\{GUID}`, with `DisplayName` taken from a per-VS-host `PresentableName`. So `JetBrains ReSharper` is a best-supported guess. It can be confirmed from a validator run: `cmd/maintained-apps/validate/windows.go` searches `programs` with a loose `LOWER(name) LIKE '%…%'` on both the catalog name and `unique_identifier`, and logs `Found app: '<DisplayName>' … Version: <ver>` after running `MutateSoftwareOnIngestion`. That reveals both the true DisplayName and the post-mutation version. The install script prints the same information. **Expect a follow-up commit correcting `unique_identifier` (and possibly the exists query) once that log lands.** ## Risks 1. **Payload is not pinned.** The SHA covers only the 69 MB bootstrapper; roughly 1.7 GB is fetched from `download.jetbrains.com` at install time. JetBrains publishes only a `windowsWeb` download for ReSharper, so there is no offline installer to point at. Install duration may exceed script timeouts. 2. **Requires Visual Studio.** With no VS present the installer has nothing to install, so the script exits 1 with a clear message. `windows-latest` runners ship Visual Studio 2022 Enterprise, so validation should be able to install. 3. **`/PerMachine=True` conflicts with pre-existing per-user installs.** JetBrains states machine-wide mode "is not compatible with existing installations in user profiles"; one must be removed first. 4. **`/SkipEtwService=True` omits the ETW host service**, so dotTrace/dotMemory profiling integration is incomplete. This is the documented tradeoff for an unattended install. 5. **Version reconciliation unconfirmed.** If the DisplayName ends in a marketing version, the JetBrains name-based version mutation fires and the validator's prefix check passes; a VS-suffixed name would instead fall back to the registry `DisplayVersion`. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [x] Timeouts are implemented and retries are limited to avoid infinite loops ## Testing - [x] `go test ./ee/maintained-apps/...` passes; both output JSON files parse; generated SHA matches the winget manifest. - [ ] QA'd all new/changed functionality manually — **not done.** Install/uninstall need a Windows host with Visual Studio; relying on the FMA Windows validator, which is also how `unique_identifier` gets confirmed. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added ReSharper to the maintained Windows applications catalog. * Added support for silent machine-wide installation and uninstallation. * Added Visual Studio compatibility checks and installation failure reporting. * Added a ReSharper icon for display in the software catalog. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
98 lines
3.4 KiB
PowerShell
98 lines
3.4 KiB
PowerShell
# Learn more about .exe install scripts:
|
|
# http://fleetdm.com/learn-more-about/exe-install-scripts
|
|
#
|
|
# ReSharper is a Visual Studio extension installed by a web bootstrapper.
|
|
# /PerMachine=True keeps it out of the SYSTEM profile, /SkipEtwService=True avoids
|
|
# an unavoidable UAC prompt, /VsVersion picks the VS instances to integrate into.
|
|
# https://resharper-support.jetbrains.com/hc/en-us/articles/207241485
|
|
|
|
$exeFilePath = "${env:INSTALLER_PATH}"
|
|
|
|
$registryTimeoutSeconds = 3300
|
|
|
|
$uninstallPaths = @(
|
|
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
|
|
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
|
|
)
|
|
|
|
function Get-ReSharperEntries {
|
|
Get-ChildItem -Path $uninstallPaths -ErrorAction SilentlyContinue |
|
|
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |
|
|
Where-Object {
|
|
$_.DisplayName -like 'JetBrains ReSharper*' -and
|
|
$_.DisplayName -notlike 'JetBrains ReSharper C++*' -and
|
|
$_.DisplayName -notlike 'JetBrains ReSharper SDK*' -and
|
|
$_.Publisher -like '*JetBrains*'
|
|
}
|
|
}
|
|
|
|
try {
|
|
|
|
$vsWhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
|
|
if (-not (Test-Path $vsWhere)) {
|
|
Write-Host "Visual Studio Installer not found at $vsWhere."
|
|
Write-Host "ReSharper is a Visual Studio extension and cannot be installed without Visual Studio."
|
|
Exit 1
|
|
}
|
|
|
|
$installationVersions = & $vsWhere -all -prerelease -products '*' -property installationVersion 2>$null
|
|
$vsMajors = @(
|
|
$installationVersions |
|
|
Where-Object { $_ -match '^\d+' } |
|
|
ForEach-Object { [int](($_ -split '\.')[0]) } |
|
|
Sort-Object -Unique -Descending
|
|
)
|
|
|
|
if ($vsMajors.Count -eq 0) {
|
|
Write-Host "No Visual Studio instances were reported by vswhere."
|
|
Write-Host "ReSharper is a Visual Studio extension and cannot be installed without Visual Studio."
|
|
Exit 1
|
|
}
|
|
|
|
$vsVersions = ($vsMajors | ForEach-Object { "$_.0" }) -join ';'
|
|
Write-Host "Visual Studio instances detected: $vsVersions"
|
|
|
|
$logFile = Join-Path $env:TEMP 'resharper-install.log'
|
|
|
|
$processOptions = @{
|
|
FilePath = "$exeFilePath"
|
|
ArgumentList = "/Silent=True /PerMachine=True /SkipEtwService=True /VsVersion=$vsVersions /LogFile=`"$logFile`""
|
|
PassThru = $true
|
|
Wait = $true
|
|
}
|
|
|
|
$process = Start-Process @processOptions
|
|
$exitCode = $process.ExitCode
|
|
Write-Host "Installer exit code: $exitCode"
|
|
|
|
$deadline = (Get-Date).AddSeconds($registryTimeoutSeconds)
|
|
$entries = @(Get-ReSharperEntries)
|
|
while ($entries.Count -eq 0 -and (Get-Date) -lt $deadline) {
|
|
$installerWasRunning = @(Get-Process -Name 'JetBrains.Platform.Installer*' -ErrorAction SilentlyContinue).Count -gt 0
|
|
Start-Sleep -Seconds 10
|
|
$entries = @(Get-ReSharperEntries)
|
|
$installerIsRunning = @(Get-Process -Name 'JetBrains.Platform.Installer*' -ErrorAction SilentlyContinue).Count -gt 0
|
|
if ($entries.Count -eq 0 -and -not $installerWasRunning -and -not $installerIsRunning) { break }
|
|
}
|
|
|
|
if ($entries.Count -eq 0) {
|
|
Write-Host "The ReSharper uninstall registry entry did not appear."
|
|
if (Test-Path $logFile) {
|
|
Write-Host "--- last 50 lines of $logFile ---"
|
|
Get-Content $logFile -Tail 50 | ForEach-Object { Write-Host $_ }
|
|
}
|
|
if ($exitCode -eq 0) { Exit 1 }
|
|
Exit $exitCode
|
|
}
|
|
|
|
foreach ($entry in $entries) {
|
|
Write-Host "Installed: DisplayName='$($entry.DisplayName)' DisplayVersion='$($entry.DisplayVersion)' Publisher='$($entry.Publisher)'"
|
|
}
|
|
|
|
Exit $exitCode
|
|
|
|
} catch {
|
|
Write-Host "Error: $_"
|
|
Exit 1
|
|
}
|