diff --git a/ee/maintained-apps/inputs/winget/resharper.json b/ee/maintained-apps/inputs/winget/resharper.json new file mode 100644 index 0000000000..c8e952021d --- /dev/null +++ b/ee/maintained-apps/inputs/winget/resharper.json @@ -0,0 +1,13 @@ +{ + "name": "ReSharper", + "slug": "resharper/windows", + "package_identifier": "JetBrains.ReSharper", + "unique_identifier": "JetBrains ReSharper", + "exists_query": "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_path": "ee/maintained-apps/inputs/winget/scripts/resharper_install.ps1", + "uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/resharper_uninstall.ps1", + "installer_arch": "x64", + "installer_type": "exe", + "installer_scope": "machine", + "default_categories": ["Developer tools"] +} diff --git a/ee/maintained-apps/inputs/winget/scripts/resharper_install.ps1 b/ee/maintained-apps/inputs/winget/scripts/resharper_install.ps1 new file mode 100644 index 0000000000..25e3b8ec23 --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/resharper_install.ps1 @@ -0,0 +1,97 @@ +# 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 +} diff --git a/ee/maintained-apps/inputs/winget/scripts/resharper_uninstall.ps1 b/ee/maintained-apps/inputs/winget/scripts/resharper_uninstall.ps1 new file mode 100644 index 0000000000..dae40585c9 --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/resharper_uninstall.ps1 @@ -0,0 +1,93 @@ +# Removes JetBrains ReSharper via its registry uninstall entries. ReSharper +# registers one per Visual Studio instance, so every match is removed, and the +# JetBrains uninstaller takes /Silent=True rather than the NSIS /S. +# https://resharper-support.jetbrains.com/hc/en-us/articles/207241485 + +$softwareNameLike = "JetBrains ReSharper*" +$publisherLike = "*JetBrains*" + +$paths = @( + 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', + 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall' +) + +$exitCode = 0 + +try { + +[array]$uninstallKeys = Get-ChildItem ` + -Path $paths ` + -ErrorAction SilentlyContinue | + ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } + +# ReSharper C++ and the ReSharper SDK are separate products. +[array]$selected = $uninstallKeys | Where-Object { + $_.DisplayName -and + $_.DisplayName -like $softwareNameLike -and + $_.DisplayName -notlike 'JetBrains ReSharper C++*' -and + $_.DisplayName -notlike 'JetBrains ReSharper SDK*' -and + $_.Publisher -like $publisherLike -and + $_.UninstallString +} + +if ($selected.Count -eq 0) { + Write-Host "Uninstall entry not found for $softwareNameLike; nothing to do." + Exit 0 +} + +Stop-Process -Name "devenv" -Force -ErrorAction SilentlyContinue +Stop-Process -Name "JetBrains.Etw.Collector.Host" -Force -ErrorAction SilentlyContinue +Stop-Process -Name "JetBrains.Platform.Satellite" -Force -ErrorAction SilentlyContinue + +foreach ($entry in $selected) { + $uninstallCommand = $entry.UninstallString + + # JetBrains stores unquoted paths that contain spaces. + $exePath = "" + $existingArgs = "" + if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') { + $exePath = $matches[1] + $existingArgs = $matches[2].Trim() + } elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { + $exePath = $matches[1] + $existingArgs = $matches[2].Trim() + } elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') { + $exePath = $matches[1] + $existingArgs = $matches[2].Trim() + } else { + Write-Host "Could not parse uninstall string: $uninstallCommand" + $exitCode = 1 + continue + } + + if ($existingArgs -notmatch '(?i)/Silent=') { + $existingArgs = ("$existingArgs /Silent=True").Trim() + } + + Write-Host "Selected entry DisplayName: $($entry.DisplayName)" + Write-Host "Uninstall command: $exePath" + Write-Host "Uninstall args: $existingArgs" + + $processOptions = @{ + FilePath = $exePath + PassThru = $true + Wait = $true + } + + if ($existingArgs -ne '') { + $processOptions.ArgumentList = $existingArgs + } + + $process = Start-Process @processOptions + Write-Host "Uninstall exit code: $($process.ExitCode)" + if ($process.ExitCode -ne 0) { + $exitCode = $process.ExitCode + } +} + +Exit $exitCode + +} catch { + Write-Host "Error: $_" + Exit 1 +} diff --git a/ee/maintained-apps/outputs/apps.json b/ee/maintained-apps/outputs/apps.json index f16afc315a..2c1867ae48 100644 --- a/ee/maintained-apps/outputs/apps.json +++ b/ee/maintained-apps/outputs/apps.json @@ -7295,6 +7295,13 @@ "unique_identifier": "Requestly", "description": "Requestly is an app for intercepting and modifying HTTP requests." }, + { + "name": "ReSharper", + "slug": "resharper/windows", + "platform": "windows", + "unique_identifier": "JetBrains ReSharper", + "description": "ReSharper is a Visual Studio extension for .NET developers." + }, { "name": "Retcon", "slug": "retcon/darwin", diff --git a/ee/maintained-apps/outputs/resharper/windows.json b/ee/maintained-apps/outputs/resharper/windows.json new file mode 100644 index 0000000000..40350109d1 --- /dev/null +++ b/ee/maintained-apps/outputs/resharper/windows.json @@ -0,0 +1,22 @@ +{ + "versions": [ + { + "version": "2026.2.0.2", + "queries": { + "exists": "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.';", + "patched": "SELECT 1 WHERE NOT EXISTS (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.' AND version_compare(version, '2026.2.0.2') < 0);" + }, + "installer_url": "https://download.jetbrains.com/resharper/dotUltimate.2026.2.0.2/JetBrains.ReSharper.2026.2.0.2.web.exe", + "install_script_ref": "97fe5e08", + "uninstall_script_ref": "78b5f38c", + "sha256": "a34ea0d4878cc88bd726ba5e1ce024fb4e62225fb76e2869c3b8ee1891a2c25b", + "default_categories": [ + "Developer tools" + ] + } + ], + "refs": { + "78b5f38c": "# Removes JetBrains ReSharper via its registry uninstall entries. ReSharper\n# registers one per Visual Studio instance, so every match is removed, and the\n# JetBrains uninstaller takes /Silent=True rather than the NSIS /S.\n# https://resharper-support.jetbrains.com/hc/en-us/articles/207241485\n\n$softwareNameLike = \"JetBrains ReSharper*\"\n$publisherLike = \"*JetBrains*\"\n\n$paths = @(\n 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',\n 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'\n)\n\n$exitCode = 0\n\ntry {\n\n[array]$uninstallKeys = Get-ChildItem `\n -Path $paths `\n -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue }\n\n# ReSharper C++ and the ReSharper SDK are separate products.\n[array]$selected = $uninstallKeys | Where-Object {\n $_.DisplayName -and\n $_.DisplayName -like $softwareNameLike -and\n $_.DisplayName -notlike 'JetBrains ReSharper C++*' -and\n $_.DisplayName -notlike 'JetBrains ReSharper SDK*' -and\n $_.Publisher -like $publisherLike -and\n $_.UninstallString\n}\n\nif ($selected.Count -eq 0) {\n Write-Host \"Uninstall entry not found for $softwareNameLike; nothing to do.\"\n Exit 0\n}\n\nStop-Process -Name \"devenv\" -Force -ErrorAction SilentlyContinue\nStop-Process -Name \"JetBrains.Etw.Collector.Host\" -Force -ErrorAction SilentlyContinue\nStop-Process -Name \"JetBrains.Platform.Satellite\" -Force -ErrorAction SilentlyContinue\n\nforeach ($entry in $selected) {\n $uninstallCommand = $entry.UninstallString\n\n # JetBrains stores unquoted paths that contain spaces.\n $exePath = \"\"\n $existingArgs = \"\"\n if ($uninstallCommand -match '^\\s*\"([^\"]+)\"\\s*(.*)$') {\n $exePath = $matches[1]\n $existingArgs = $matches[2].Trim()\n } elseif ($uninstallCommand -match '(?i)^\\s*(.+?\\.exe)\\s*(.*)$') {\n $exePath = $matches[1]\n $existingArgs = $matches[2].Trim()\n } elseif ($uninstallCommand -match '^\\s*(\\S+)\\s*(.*)$') {\n $exePath = $matches[1]\n $existingArgs = $matches[2].Trim()\n } else {\n Write-Host \"Could not parse uninstall string: $uninstallCommand\"\n $exitCode = 1\n continue\n }\n\n if ($existingArgs -notmatch '(?i)/Silent=') {\n $existingArgs = (\"$existingArgs /Silent=True\").Trim()\n }\n\n Write-Host \"Selected entry DisplayName: $($entry.DisplayName)\"\n Write-Host \"Uninstall command: $exePath\"\n Write-Host \"Uninstall args: $existingArgs\"\n\n $processOptions = @{\n FilePath = $exePath\n PassThru = $true\n Wait = $true\n }\n\n if ($existingArgs -ne '') {\n $processOptions.ArgumentList = $existingArgs\n }\n\n $process = Start-Process @processOptions\n Write-Host \"Uninstall exit code: $($process.ExitCode)\"\n if ($process.ExitCode -ne 0) {\n $exitCode = $process.ExitCode\n }\n}\n\nExit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n", + "97fe5e08": "# Learn more about .exe install scripts:\n# http://fleetdm.com/learn-more-about/exe-install-scripts\n#\n# ReSharper is a Visual Studio extension installed by a web bootstrapper.\n# /PerMachine=True keeps it out of the SYSTEM profile, /SkipEtwService=True avoids\n# an unavoidable UAC prompt, /VsVersion picks the VS instances to integrate into.\n# https://resharper-support.jetbrains.com/hc/en-us/articles/207241485\n\n$exeFilePath = \"${env:INSTALLER_PATH}\"\n\n# Under production's 1 hour cap; the wait below also gives up early.\n$registryTimeoutSeconds = 3300\n\n$uninstallPaths = @(\n 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',\n 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'\n)\n\nfunction Get-ReSharperEntries {\n Get-ChildItem -Path $uninstallPaths -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |\n Where-Object {\n $_.DisplayName -like 'JetBrains ReSharper*' -and\n $_.DisplayName -notlike 'JetBrains ReSharper C++*' -and\n $_.DisplayName -notlike 'JetBrains ReSharper SDK*' -and\n $_.Publisher -like '*JetBrains*'\n }\n}\n\ntry {\n\n$vsWhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\\Installer\\vswhere.exe'\nif (-not (Test-Path $vsWhere)) {\n Write-Host \"Visual Studio Installer not found at $vsWhere.\"\n Write-Host \"ReSharper is a Visual Studio extension and cannot be installed without Visual Studio.\"\n Exit 1\n}\n\n$installationVersions = & $vsWhere -all -prerelease -products '*' -property installationVersion 2>$null\n$vsMajors = @(\n $installationVersions |\n Where-Object { $_ -match '^\\d+' } |\n ForEach-Object { [int](($_ -split '\\.')[0]) } |\n Sort-Object -Unique -Descending\n)\n\nif ($vsMajors.Count -eq 0) {\n Write-Host \"No Visual Studio instances were reported by vswhere.\"\n Write-Host \"ReSharper is a Visual Studio extension and cannot be installed without Visual Studio.\"\n Exit 1\n}\n\n$vsVersions = ($vsMajors | ForEach-Object { \"$_.0\" }) -join ';'\nWrite-Host \"Visual Studio instances detected: $vsVersions\"\n\n$logFile = Join-Path $env:TEMP 'resharper-install.log'\n\n$processOptions = @{\n FilePath = \"$exeFilePath\"\n ArgumentList = \"/Silent=True /PerMachine=True /SkipEtwService=True /VsVersion=$vsVersions /LogFile=`\"$logFile`\"\"\n PassThru = $true\n Wait = $true\n}\n\n$process = Start-Process @processOptions\n$exitCode = $process.ExitCode\nWrite-Host \"Installer exit code: $exitCode\"\n\n# The bootstrapper can outlive its own exit code, so poll for the uninstall\n# registry entry (what osquery reports). The installer process is sampled at both\n# ends of the interval so neither one starting nor one exiting mid-sleep is\n# mistaken for nothing left to wait for.\n$deadline = (Get-Date).AddSeconds($registryTimeoutSeconds)\n$entries = @(Get-ReSharperEntries)\nwhile ($entries.Count -eq 0 -and (Get-Date) -lt $deadline) {\n $installerWasRunning = @(Get-Process -Name 'JetBrains.Platform.Installer*' -ErrorAction SilentlyContinue).Count -gt 0\n Start-Sleep -Seconds 10\n $entries = @(Get-ReSharperEntries)\n $installerIsRunning = @(Get-Process -Name 'JetBrains.Platform.Installer*' -ErrorAction SilentlyContinue).Count -gt 0\n if ($entries.Count -eq 0 -and -not $installerWasRunning -and -not $installerIsRunning) { break }\n}\n\nif ($entries.Count -eq 0) {\n Write-Host \"The ReSharper uninstall registry entry did not appear.\"\n if (Test-Path $logFile) {\n Write-Host \"--- last 50 lines of $logFile ---\"\n Get-Content $logFile -Tail 50 | ForEach-Object { Write-Host $_ }\n }\n if ($exitCode -eq 0) { Exit 1 }\n Exit $exitCode\n}\n\nforeach ($entry in $entries) {\n Write-Host \"Installed: DisplayName='$($entry.DisplayName)' DisplayVersion='$($entry.DisplayVersion)' Publisher='$($entry.Publisher)'\"\n}\n\nExit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n" + } +} diff --git a/frontend/pages/SoftwarePage/components/icons/Resharper.tsx b/frontend/pages/SoftwarePage/components/icons/Resharper.tsx new file mode 100644 index 0000000000..75dc545d88 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/Resharper.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; + +import type { SVGProps } from "react"; + +const Resharper = (props: SVGProps) => ( + + + +); +export default Resharper; diff --git a/frontend/pages/SoftwarePage/components/icons/index.ts b/frontend/pages/SoftwarePage/components/icons/index.ts index 611f026362..07f53a910f 100644 --- a/frontend/pages/SoftwarePage/components/icons/index.ts +++ b/frontend/pages/SoftwarePage/components/icons/index.ts @@ -857,6 +857,7 @@ import RemoteBuddy from "./RemoteBuddy"; import RemoteDesktopManager from "./RemoteDesktopManager"; import Reqable from "./Reqable"; import Requestly from "./Requestly"; +import Resharper from "./Resharper"; import Retcon from "./Retcon"; import Retroarch from "./Retroarch"; import Retrobatch from "./Retrobatch"; @@ -2028,6 +2029,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = { "remote desktop manager": RemoteDesktopManager, reqable: Reqable, requestly: Requestly, + resharper: Resharper, retcon: Retcon, retroarch: Retroarch, retrobatch: Retrobatch, diff --git a/website/assets/images/app-icon-resharper-60x60@2x.png b/website/assets/images/app-icon-resharper-60x60@2x.png new file mode 100644 index 0000000000..00ed2eeb5f Binary files /dev/null and b/website/assets/images/app-icon-resharper-60x60@2x.png differ