Skip to content

Commit f55d901

Browse files
ADO Lens 0.12.1
ADO Lens 0.12.1
2 parents 070d107 + ab0bdfb commit f55d901

8 files changed

Lines changed: 112 additions & 19 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## 0.12.1 — 2026-11-04
4+
5+
- Fixed: the install (`get.ps1` / `install.ps1`) could wait for Claude Desktop to close forever, although it was
6+
closed:
7+
- only Claude Desktop of your own Windows session counts now - on a server with remote desktop sessions another
8+
user's Claude was seen too (in an elevated window even with its path), and it never closes for you;
9+
- the Claude Code CLI shipped with Claude Desktop (claude.exe under …\claude-code\…) no longer counts as Claude
10+
Desktop;
11+
- while waiting it names what is still running (PID and path) every 20 seconds; **S** skips the registration
12+
(`adolens register` later), and after 10 minutes it is skipped by itself;
13+
- run from a terminal inside Claude (e.g. its Code tab, or `ado_upgrade` of a ZIP install), it installs the program
14+
and says to register it from a regular window, instead of waiting for Claude to close itself.
15+
- `adolens register`, when Claude Desktop runs, lists its processes in this session.
16+
- README: run the one-line install in a regular PowerShell window, not as administrator and not inside Claude.
17+
318
## 0.12.0 — 2026-11-04
419

520
Comments can be fixed and removed, and any change can be undone.

‎README.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ Azure DevOps is a trademark of Microsoft.
3333

3434
## Installing
3535

36-
**The easy way — one line.** In a regular PowerShell window (not as administrator) paste:
36+
**The easy way — one line.** In a regular PowerShell window (Start menu → Windows PowerShell; not as administrator, not
37+
a terminal inside Claude) paste:
3738

3839
```powershell
3940
irm https://raw.githubusercontent.com/willowdev-code/ado-lens/main/get.ps1 | iex
@@ -96,7 +97,10 @@ The installer, in six steps with a default for every question (Enter accepts it)
9697
6. registers the `ado-lens` server in Claude Desktop and Claude Code.
9798

9899
Questions are in Polish on a Polish Windows, in English otherwise. When the registration has to change while Claude Desktop runs, it asks you to quit Claude, waits and starts it
99-
again. Running `install.ps1` from a newer package updates the program and keeps settings, PATs and metadata; an
100+
again. While it waits it names what is still running (PID and path, to find in Task Manager → Details); only your own
101+
Windows session counts, so other people's Claude on a shared server does not hold it up. **S** skips the registration
102+
(register later with `adolens register`), and after 10 minutes it is skipped by itself. Run from a terminal inside
103+
Claude, it installs the program and tells you to register it from a regular window. Running `install.ps1` from a newer package updates the program and keeps settings, PATs and metadata; an
100104
`adolens.exe` used by an open session is renamed to `*.old`, so that session keeps working.
101105

102106
Useful parameters:

‎README.pl.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ Azure DevOps jest znakiem towarowym Microsoftu.
3333

3434
## Instalacja
3535

36-
**Najprościej — jedna linijka.** W zwykłym oknie PowerShella (nie jako administrator) wklej:
36+
**Najprościej — jedna linijka.** W zwykłym oknie PowerShella (menu Start → Windows PowerShell; nie jako administrator,
37+
nie w terminalu wewnątrz Claude'a) wklej:
3738

3839
```powershell
3940
irm https://raw.githubusercontent.com/willowdev-code/ado-lens/main/get.ps1 | iex
@@ -99,7 +100,10 @@ Instalator działa w sześciu krokach, każde pytanie ma odpowiedź domyślną (
99100

100101
Na polskim Windowsie pytania są po polsku, w innym przypadku po angielsku.
101102
Jeśli rejestracja musi się zmienić, a Claude Desktop działa, instalator prosi o zamknięcie Claude'a, czeka i uruchamia go
102-
ponownie. Uruchomienie `install.ps1` z nowszej paczki aktualizuje program i zostawia ustawienia, PAT-y i metadane;
103+
ponownie. W trakcie czekania pokazuje, co jeszcze działa (PID i ścieżka, do znalezienia w Menedżerze zadań → Szczegóły);
104+
liczy się tylko twoja sesja Windows, więc Claude innych osób na wspólnym serwerze go nie blokuje. **S** pomija
105+
rejestrację (później: `adolens register`), a po 10 minutach pomija ją sam. Uruchomiony z terminala wewnątrz Claude'a
106+
instaluje program i mówi, żeby zarejestrować go ze zwykłego okna. Uruchomienie `install.ps1` z nowszej paczki aktualizuje program i zostawia ustawienia, PAT-y i metadane;
103107
`adolens.exe` używany przez otwartą sesję dostaje nazwę `*.old`, więc ta sesja działa dalej.
104108

105109
Przydatne parametry:

‎install.ps1‎

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,26 @@ param(
3333
$ErrorActionPreference = 'Stop'
3434
function Say($m) { Write-Host " $m" }
3535
function Head($m) { Write-Host "`n$m" -ForegroundColor Cyan }
36+
# Claude Desktop of this Windows session only: on a server with remote desktop sessions other people's Claude runs too
37+
# (and an elevated window sees their paths). The Claude Code CLI is also claude.exe, also under the Claude folders
38+
# (...\claude-code\...), but it is not the desktop app and does not need to be closed.
39+
$mySession = (Get-Process -Id $PID).SessionId
40+
function Test-DesktopPath([string] $path) {
41+
$path -and $path -notlike '*\claude-code\*' -and ($path -like '*\WindowsApps\Claude_*' -or $path -like '*\AnthropicClaude\*')
42+
}
3643
function Get-ClaudeDesktop {
37-
Get-Process -Name claude -ErrorAction SilentlyContinue |
38-
Where-Object { $_.Path -like '*\WindowsApps\Claude_*' -or $_.Path -like '*\AnthropicClaude\*' }
44+
Get-Process -Name claude -ErrorAction SilentlyContinue | Where-Object { $_.SessionId -eq $mySession -and (Test-DesktopPath $_.Path) }
45+
}
46+
# Run from a terminal inside Claude (e.g. its Code tab): Claude cannot be closed without closing this script too.
47+
function Test-InsideClaude {
48+
$id = $PID
49+
for ($i = 0; $i -lt 15 -and $id; $i++) {
50+
$p = Get-CimInstance Win32_Process -Filter "ProcessId=$id" -ErrorAction SilentlyContinue
51+
if (-not $p) { break }
52+
if ($p.Name -ieq 'claude.exe' -and (Test-DesktopPath $p.ExecutablePath)) { return $true }
53+
$id = $p.ParentProcessId
54+
}
55+
return $false
3956
}
4057
function Start-ClaudeDesktop {
4158
$pkg = Get-AppxPackage -Name Claude -ErrorAction SilentlyContinue | Select-Object -First 1
@@ -63,13 +80,42 @@ else {
6380
$needsRegister = ($LASTEXITCODE -eq 3)
6481
}
6582
$closedClaude = $false
83+
$skippedRegister = $false
6684
if ($needsRegister -and (Get-ClaudeDesktop)) {
6785
Write-Host ' The Claude registration has to change and Claude Desktop is running.' -ForegroundColor Yellow
68-
Write-Host ' Quit Claude now: tray icon (next to the clock) -> right click -> Quit. Waiting...' -ForegroundColor Yellow
69-
while (Get-ClaudeDesktop) { Start-Sleep -Seconds 1 }
70-
Start-Sleep -Seconds 2
71-
$closedClaude = $true
72-
Say 'Claude is closed'
86+
if (Test-InsideClaude) {
87+
Write-Host ' This window runs inside Claude, which cannot be closed while it runs this script.' -ForegroundColor Yellow
88+
Write-Host ' The program is installed now; to register it, quit Claude and run in a regular PowerShell window:' -ForegroundColor Yellow
89+
Write-Host " & `"$exe`" register"
90+
$needsRegister = $false
91+
$skippedRegister = $true
92+
}
93+
else {
94+
Write-Host ' Quit Claude now: tray icon (next to the clock) -> right click -> Quit.' -ForegroundColor Yellow
95+
Write-Host ' Waiting... (S = skip the registration now and register later with: adolens register)' -ForegroundColor Yellow
96+
$waited = 0
97+
while (Get-ClaudeDesktop) {
98+
# Every 20 seconds: what is still running, so it can be found in Task Manager (Details tab, by PID).
99+
if ($waited % 20 -eq 0) {
100+
foreach ($p in Get-ClaudeDesktop) { Say "still running: PID $($p.Id) $($p.Path)" }
101+
}
102+
$key = $null
103+
try { if ([Console]::KeyAvailable) { $key = [Console]::ReadKey($true).Key } } catch { }
104+
if ($key -eq 'S' -or $waited -ge 600) {
105+
if ($waited -ge 600) { Say 'Claude is still running after 10 minutes - skipping the registration' }
106+
$needsRegister = $false
107+
$skippedRegister = $true
108+
break
109+
}
110+
Start-Sleep -Seconds 1
111+
$waited++
112+
}
113+
if (-not $skippedRegister) {
114+
Start-Sleep -Seconds 2
115+
$closedClaude = $true
116+
Say 'Claude is closed'
117+
}
118+
}
73119
}
74120

75121
Head "2/6 Copying files to $InstallDir"
@@ -161,6 +207,9 @@ if ($needsRegister) {
161207
elseif ($NoRegister) {
162208
Say "skipped (-NoRegister). Later: `"$exe`" register"
163209
}
210+
elseif ($skippedRegister) {
211+
Write-Host " skipped - quit Claude Desktop, then run: & `"$exe`" register" -ForegroundColor Yellow
212+
}
164213
else {
165214
Say 'already registered'
166215
}

‎src/AdoLens/AdoLens.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<AssemblyTitle>ADO Lens for Azure DevOps</AssemblyTitle>
2626
<Description>Azure DevOps (work items, boards, wiki, Git and TFVC, pipelines) served to AI assistants over MCP</Description>
2727
<Copyright>Copyright © 2026 WillowDev</Copyright>
28-
<Version>0.12.0</Version>
28+
<Version>0.12.1</Version>
2929
</PropertyGroup>
3030

3131
<ItemGroup>

‎src/AdoLens/ClaudeConfigs.cs‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,24 @@ public static List<string> EntriesWithPlainTextPat(string path)
104104
}
105105

106106
/// <summary>Claude Desktop keeps its configuration in memory and writes it back, so entries must change while it is closed.</summary>
107-
public static bool IsClaudeDesktopRunning()
107+
public static bool IsClaudeDesktopRunning() => ClaudeDesktopProcesses().Count > 0;
108+
109+
/// <summary>
110+
/// The Claude Desktop processes of this Windows session: on a server with remote desktop sessions other people's
111+
/// Claude runs too, and it does not hold this user's configuration. The Claude Code CLI is also claude.exe, also under
112+
/// the Claude folders (…\claude-code\…), but it is not the desktop app.
113+
/// </summary>
114+
public static List<(int Id, string Path)> ClaudeDesktopProcesses()
108115
{
116+
var session = Process.GetCurrentProcess().SessionId;
117+
var found = new List<(int, string)>();
109118
foreach (var p in Process.GetProcessesByName("claude"))
110119
{
111120
try
112121
{
122+
if (p.SessionId != session) continue;
113123
var file = p.MainModule?.FileName ?? "";
114-
// The Claude Code CLI is also claude.exe; only the desktop app lives in these folders.
115-
if (file.Contains(@"\WindowsApps\Claude_", StringComparison.OrdinalIgnoreCase)
116-
|| file.Contains(@"\AnthropicClaude\", StringComparison.OrdinalIgnoreCase))
117-
return true;
124+
if (IsDesktopPath(file)) found.Add((p.Id, file));
118125
}
119126
catch (Exception ex) when (ex is System.ComponentModel.Win32Exception or InvalidOperationException)
120127
{
@@ -125,9 +132,13 @@ public static bool IsClaudeDesktopRunning()
125132
p.Dispose();
126133
}
127134
}
128-
return false;
135+
return found;
129136
}
130137

138+
internal static bool IsDesktopPath(string file) =>
139+
!file.Contains(@"\claude-code\", StringComparison.OrdinalIgnoreCase)
140+
&& (file.Contains(@"\WindowsApps\Claude_", StringComparison.OrdinalIgnoreCase) || file.Contains(@"\AnthropicClaude\", StringComparison.OrdinalIgnoreCase));
141+
131142
static bool IsSecretVar(string key) =>
132143
key.Contains("PAT", StringComparison.OrdinalIgnoreCase) || key.Contains("TOKEN", StringComparison.OrdinalIgnoreCase);
133144

‎src/AdoLens/Cli.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,8 @@ public int Register()
402402
if (pending.Any(t => t.IsDesktop) && !cl.Flag("force") && ClaudeConfigs.IsClaudeDesktopRunning())
403403
{
404404
Console.Error.WriteLine("Claude Desktop is running and would overwrite the change. Quit it (tray icon -> Quit) and run this again,");
405-
Console.Error.WriteLine("or use install.ps1, which waits for Claude to close and starts it again.");
405+
Console.Error.WriteLine("or use install.ps1, which waits for Claude to close and starts it again. Still running in this session:");
406+
foreach (var (pid, file) in ClaudeConfigs.ClaudeDesktopProcesses()) Console.Error.WriteLine($" PID {pid} {file}");
406407
return ClaudeRunning;
407408
}
408409

‎tests/AdoLens.Tests/ImprovementTests.cs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ public void Code_can_live_in_another_project_than_the_work_items()
5151
}
5252
}
5353

54+
[Theory]
55+
[InlineData(@"C:\Program Files\WindowsApps\Claude_4.1_x64__abc\app\Claude.exe", true)]
56+
[InlineData(@"C:\Users\x\AppData\Local\AnthropicClaude\app-1.2.3\claude.exe", true)]
57+
[InlineData(@"C:\Users\x\AppData\Local\AnthropicClaude\app-1.2.3\resources\claude-code\claude.exe", false)]
58+
[InlineData(@"C:\Users\x\AppData\Local\Claude-Data\claude_abc\claude-code\2.1\claude.exe", false)]
59+
[InlineData(@"C:\Users\x\.local\bin\claude.exe", false)]
60+
public void Only_the_desktop_app_counts_as_a_running_Claude_Desktop(string path, bool desktop) =>
61+
Assert.Equal(desktop, ClaudeConfigs.IsDesktopPath(path));
62+
5463
[Theory]
5564
[InlineData("Main", "DEV")]
5665
[InlineData("SIT", "UAT")]

0 commit comments

Comments
 (0)