dsh-windows-tray 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/install.ps1 CHANGED
@@ -1,184 +1,228 @@
1
- # install.ps1 —— DSH 系统托盘 一键部署(便携包内运行)
2
- # 用法: pwsh -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 [-DshRoot <目录>] [-PwshPath <路径>] [-NodePath <路径>] [-Port <端口>] [-DshHome <数据目录>] [-Auto] [-NoTrayStart] [-NoShortcuts]
3
- # 流程: 探测本机路径 -> 探测端口/数据目录并确认 -> 安装到 ~\.dsh\launcher -> 回填全部读取点(含端口两处+DSH_HOME) -> 创建快捷方式 -> 启动托盘
4
- # v1.0.2: 端口/数据目录实测探测 + 展示确认 + 回填全部读取点 + 自动创建日志目录 (修: param 默认值覆盖 lib 端口 / DSH_HOME 不设 / 日志目录缺失静默丢日志)
5
- param(
6
- [string]$DshRoot, # dsh 安装根目录 (含 dsh.cmd); 缺省自动探测
7
- [string]$PwshPath, # pwsh.exe 路径; 缺省自动探测
8
- [string]$NodePath, # node.exe 路径; 缺省自动探测
9
- [int]$Port = 0, # Web 端口; 0=从运行中的 web 进程命令行探测
10
- [string]$DshHome, # 数据目录 (含 settings.yaml); 空=自动探测
11
- [switch]$Auto, # 非交互: 直接采用探测值不确认 (自更新等隐藏窗口场景)
12
- [switch]$NoTrayStart, # 部署后不自动启动托盘
13
- [switch]$NoShortcuts # 不创建快捷方式
14
- )
15
- $ErrorActionPreference = 'Stop'
16
- $pkgRoot = $PSScriptRoot
17
- $dst = Join-Path $env:USERPROFILE '.dsh\launcher'
18
- $logDir = Join-Path $env:USERPROFILE '.dsh\logs'
19
-
20
- Write-Host '== DSH 系统托盘 一键部署 ==' -ForegroundColor Cyan
21
-
22
- # ---- 探测: 实际 Web 端口 (从运行中的 web 进程命令行提取, 不写死默认值) ----
23
- function Find-WebPort {
24
- $procs = @(Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
25
- Where-Object { $_.CommandLine -match 'web' -and $_.CommandLine -match '--port' })
26
- foreach ($p in $procs) {
27
- if ($p.CommandLine -match '--port\s+(\d+)') { return [int]$Matches[1] }
28
- }
29
- return $null
30
- }
31
-
32
- # ---- 探测: 实际数据目录 (特征 = settings.yaml; 环境变量优先, 其次默认 ~\.dsh) ----
33
- function Find-DshHome {
34
- if ($env:DSH_HOME -and (Test-Path (Join-Path $env:DSH_HOME 'settings.yaml'))) { return $env:DSH_HOME }
35
- $def = Join-Path $env:USERPROFILE '.dsh'
36
- if (Test-Path (Join-Path $def 'settings.yaml')) { return $def }
37
- return $null
38
- }
39
-
40
- # ---- 交互确认 (Auto 或 stdin 不可用时直接采用探测值) ----
41
- function Confirm-Value([string]$label, [string]$current) {
42
- if ($Auto) { return $current }
43
- try {
44
- $answer = Read-Host "$label [$current]"
45
- if (-not [string]::IsNullOrWhiteSpace($answer)) { return $answer.Trim() }
46
- } catch { } # 非交互环境: Read-Host 失败 -> 采用探测值
47
- return $current
48
- }
49
-
50
- # 1) 探测 dsh 安装根目录
51
- if (-not $DshRoot) {
52
- $candidates = @(
53
- (Join-Path $pkgRoot '..\..'), # 包直接放在 dsh 安装根内: <根>\dist\dsh-tray-package\
54
- 'D:\Program Files (x86)\dsh',
55
- 'C:\Program Files\dsh',
56
- (Join-Path $env:USERPROFILE 'dsh'),
57
- (Join-Path $env:LOCALAPPDATA 'dsh')
58
- )
59
- foreach ($c in $candidates) {
60
- $c = [System.IO.Path]::GetFullPath($c)
61
- if (Test-Path (Join-Path $c 'dsh.cmd')) { $DshRoot = $c; break }
62
- }
63
- }
64
- $dsdCmd = Join-Path $DshRoot 'dsh.cmd'
65
- if (-not (Test-Path $dsdCmd)) { throw "未找到 dsh 安装目录 (缺少 dsh.cmd: $dsdCmd)。请用 -DshRoot 指定。" }
66
- Write-Host "[1/7] dsh 根目录: $DshRoot" -ForegroundColor Green
67
-
68
- # 2) 探测 pwsh (PowerShell 7)
69
- if (-not $PwshPath) {
70
- $cmd = Get-Command pwsh -ErrorAction SilentlyContinue
71
- if ($cmd) { $PwshPath = $cmd.Source }
72
- elseif (Test-Path 'C:\Program Files\PowerShell\7\pwsh.exe') { $PwshPath = 'C:\Program Files\PowerShell\7\pwsh.exe' }
73
- elseif (Test-Path 'D:\Program Files (x86)\PowerShell-7.6.4\pwsh.exe') { $PwshPath = 'D:\Program Files (x86)\PowerShell-7.6.4\pwsh.exe' }
74
- }
75
- if (-not $PwshPath -or -not (Test-Path $PwshPath)) { throw "未找到 pwsh (PowerShell 7)。请用 -PwshPath 指定。" }
76
- # 版本校验: 托盘常驻进程依赖 PowerShell 7.x (5.1 只能降级运行)
77
- $pwshVer = & $PwshPath -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' 2>$null
78
- if ($pwshVer -notmatch '^7\.') { throw "pwsh 必须是 PowerShell 7.x (检测到: $pwshVer)。请安装 PowerShell 7 或用 -PwshPath 指定。" }
79
- Write-Host "[2/7] pwsh: $PwshPath (v$pwshVer)" -ForegroundColor Green
80
-
81
- # 3) 探测 node
82
- if (-not $NodePath) {
83
- $cmd = Get-Command node -ErrorAction SilentlyContinue
84
- if ($cmd) { $NodePath = $cmd.Source }
85
- elseif (Test-Path 'C:\Program Files\nodejs\node.exe') { $NodePath = 'C:\Program Files\nodejs\node.exe' }
86
- }
87
- if (-not $NodePath -or -not (Test-Path $NodePath)) { throw "未找到 node.exe。请用 -NodePath 指定。" }
88
- # 版本校验: 需要 ≥ 18 (dsh 本体运行要求)
89
- $nodeVer = & $NodePath -v 2>$null
90
- if ($nodeVer -notmatch '^v(\d+)\.') { throw "无法识别 node 版本 (检测到: $nodeVer)。请用 -NodePath 指定有效 node.exe。" }
91
- if ([int]$Matches[1] -lt 18) { throw "node 必须 ≥ 18 (检测到: $nodeVer)。请升级 Node.js 或用 -NodePath 指定。" }
92
- Write-Host "[3/7] node: $NodePath (v$($Matches[1]))" -ForegroundColor Green
93
-
94
- # 4) 探测 Web 端口 + 数据目录, 展示确认
95
- if ($Port -eq 0) {
96
- $probed = Find-WebPort
97
- if ($probed) { $Port = $probed }
98
- }
99
- if (-not $DshHome) {
100
- $probed = Find-DshHome
101
- if ($probed) { $DshHome = $probed }
102
- }
103
- if ($Port -eq 0) { $Port = 3088; Write-Host '警告: 未检测到运行中的 web 进程, 端口取包默认 3088 (如不符请用 -Port 指定)' -ForegroundColor Yellow }
104
- if (-not $DshHome) { $DshHome = Join-Path $env:USERPROFILE '.dsh'; Write-Host "警告: 未探测到数据目录 (无 settings.yaml), 取默认 $DshHome (如不符请用 -DshHome 指定)" -ForegroundColor Yellow }
105
- $Port = [int](Confirm-Value 'Web 端口' "$Port")
106
- $DshHome = Confirm-Value '数据目录' $DshHome
107
- Write-Host "[4/7] Web 端口: $Port ; 数据目录: $DshHome" -ForegroundColor Green
108
-
109
- # 5) 安装到 ~\.dsh\launcher
110
- New-Item -ItemType Directory -Force -Path $dst | Out-Null
111
- Copy-Item -Path (Join-Path $pkgRoot 'launcher\*') -Destination $dst -Recurse -Force
112
- Write-Host ('[5/7] 已安装到 ' + $dst) -ForegroundColor Green
113
-
114
- # 6) 回填全部读取点 + 自动创建日志目录
115
- $lib = Join-Path $dst 'dsh-lib.ps1'
116
- $tray = Join-Path $dst 'dsh-tray.ps1'
117
- $bin = Join-Path $DshRoot 'node_modules\@deepseek-ai\dsh\lib\bin.js'
118
- if (-not (Test-Path $bin)) { Write-Host "警告: 未找到 $bin (dsh 包结构可能不同), 请检查 dsh-lib.ps1 的 \$BIN" -ForegroundColor Yellow }
119
- $map = [ordered]@{
120
- '$DSH_CMD = ' = "`$DSH_CMD = '$($DshRoot -replace "'", "''")\dsh.cmd'"
121
- '$NODE = ' = "`$NODE = '$($NodePath -replace "'", "''")'"
122
- '$BIN = ' = "`$BIN = '$($bin -replace "'", "''")'"
123
- '$WORKDIR = ' = "`$WORKDIR = '$($DshRoot -replace "'", "''")'"
124
- '$PORT = ' = "`$PORT = $Port"
125
- '$PWSH = ' = "`$PWSH = '$($PwshPath -replace "'", "''")'"
126
- }
127
- $lines = Get-Content $lib
128
- for ($i = 0; $i -lt $lines.Count; $i++) {
129
- foreach ($k in $map.Keys) {
130
- if ($lines[$i].StartsWith($k)) { $lines[$i] = $map[$k]; break }
131
- }
132
- }
133
- # DSH_HOME: 先清旧行, 非默认数据目录时写入 (默认 ~\.dsh 不设 = dsh 原生行为)
134
- $lines = @($lines | Where-Object { $_ -notmatch '^\$env:DSH_HOME\s*=' })
135
- $defaultHome = Join-Path $env:USERPROFILE '.dsh'
136
- if ($DshHome -ne $defaultHome) {
137
- $lines = @($lines | ForEach-Object {
138
- if ($_ -match '^\$WORKDIR') { $_; "`$env:DSH_HOME = '$($DshHome -replace "'", "''")' # 数据目录 (install 回填)" }
139
- else { $_ }
140
- })
141
- }
142
- Set-Content -Path $lib -Value $lines -Encoding utf8
143
- # dsh-tray.ps1 param 默认值 (点源 lib 后 $PORT = $Port 会把 lib 改好的端口覆盖回去, 必须两处一致)
144
- # 注意: param 块是多行格式 (param( 换行 [int]$Port = …), 只匹配参数行本身
145
- $trayText = Get-Content $tray -Raw
146
- $trayText = $trayText -replace '\[int\]\$Port = \d+', "[int]`$Port = $Port"
147
- Set-Content -Path $tray -Value $trayText -Encoding utf8
148
- # 自动创建日志目录 (缺失时 Add-Content -ErrorAction SilentlyContinue 会静默丢日志, 零排查线索)
149
- New-Item -ItemType Directory -Force -Path $logDir | Out-Null
150
- Write-Host "[6/7] 已回填 (端口 $Port ×2处 + DSH_HOME) + 日志目录就绪" -ForegroundColor Green
151
-
152
- # 快捷方式: 开机自启 + 桌面
153
- if (-not $NoShortcuts) {
154
- $ws = New-Object -ComObject WScript.Shell
155
- $icon = Join-Path $dst 'icons\dsh-tray-running.ico,0'
156
- $argsLine = '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + (Join-Path $dst 'dsh-tray.ps1') + '"'
157
- foreach ($target in @(
158
- @{ Dir = [Environment]::GetFolderPath('Startup'); Name = 'DSH 系统托盘.lnk'; Desc = 'DSH 系统托盘 - 鲸鱼图标=运行状态, 右键控制' },
159
- @{ Dir = [Environment]::GetFolderPath('Desktop'); Name = 'DSH 系统托盘.lnk'; Desc = 'DSH 系统托盘 - 双击唤起' }
160
- )) {
161
- $lnk = $ws.CreateShortcut((Join-Path $target.Dir $target.Name))
162
- $lnk.TargetPath = $PwshPath
163
- $lnk.Arguments = $argsLine
164
- $lnk.WorkingDirectory = $dst
165
- $lnk.IconLocation = $icon
166
- $lnk.WindowStyle = 7
167
- $lnk.Description = $target.Desc
168
- $lnk.Save()
169
- }
170
- Write-Host '快捷方式: 开机自启 + 桌面 已创建' -ForegroundColor Green
171
- }
172
-
173
- # 启动托盘
174
- if (-not $NoTrayStart) {
175
- Start-Process -FilePath $PwshPath -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-WindowStyle', 'Hidden', '-File', (Join-Path $dst 'dsh-tray.ps1')) -WindowStyle Hidden
176
- Write-Host '托盘已启动 (若图标在 ^ 溢出区, 拖到任务栏即可)' -ForegroundColor Green
177
- }
178
-
179
- Write-Host ''
180
- Write-Host '部署完成! 常用命令:' -ForegroundColor Cyan
181
- Write-Host " 手动唤起托盘: 桌面「DSH 系统托盘」/ 控制台选项 5"
182
- Write-Host ' 取消自启: 删除启动文件夹里的「DSH 系统托盘.lnk」'
183
- Write-Host ' 卸载: 删除快捷方式 + 删除 ~\.dsh\launcher'
184
- Write-Host " 验证: $logDir\dsh-tray.log 首行「托盘启动 (…端口 $Port)」" -ForegroundColor Cyan
1
+ # install.ps1 —— DSH 系统托盘 一键部署(便携包内运行)
2
+ # 用法: pwsh -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 [-DshRoot <目录>] [-PwshPath <路径>] [-NodePath <路径>] [-Port <端口>] [-DshHome <数据目录>] [-Auto] [-NoTrayStart] [-NoShortcuts]
3
+ # 流程: 探测本机路径 -> 探测端口/数据目录并确认 -> 安装到 ~\.dsh\launcher -> 回填全部读取点(含端口两处+DSH_HOME) -> 创建快捷方式 -> 启动托盘
4
+ # v1.0.2: 端口/数据目录实测探测 + 展示确认 + 回填全部读取点 + 自动创建日志目录 (修: param 默认值覆盖 lib 端口 / DSH_HOME 不设 / 日志目录缺失静默丢日志)
5
+ # v1.0.3: 停止确认框枚举修复(DefaultButton2→Button2, Win11 .NET 崩溃框根因) + AUMID 通知身份(开始菜单快捷方式+预授权, 气泡不再显示为 PowerShell)
6
+ param(
7
+ [string]$DshRoot, # dsh 安装根目录 (含 dsh.cmd); 缺省自动探测
8
+ [string]$PwshPath, # pwsh.exe 路径; 缺省自动探测
9
+ [string]$NodePath, # node.exe 路径; 缺省自动探测
10
+ [int]$Port = 0, # Web 端口; 0=从运行中的 web 进程命令行探测
11
+ [string]$DshHome, # 数据目录 (含 settings.yaml); 空=自动探测
12
+ [switch]$Auto, # 非交互: 直接采用探测值不确认 (自更新等隐藏窗口场景)
13
+ [switch]$NoTrayStart, # 部署后不自动启动托盘
14
+ [switch]$NoShortcuts # 不创建快捷方式
15
+ )
16
+ $ErrorActionPreference = 'Stop'
17
+ $pkgRoot = $PSScriptRoot
18
+ $dst = Join-Path $env:USERPROFILE '.dsh\launcher'
19
+ $logDir = Join-Path $env:USERPROFILE '.dsh\logs'
20
+
21
+ Write-Host '== DSH 系统托盘 一键部署 ==' -ForegroundColor Cyan
22
+
23
+ # ---- 探测: 实际 Web 端口 (从运行中的 web 进程命令行提取, 不写死默认值) ----
24
+ function Find-WebPort {
25
+ $procs = @(Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
26
+ Where-Object { $_.CommandLine -match 'web' -and $_.CommandLine -match '--port' })
27
+ foreach ($p in $procs) {
28
+ if ($p.CommandLine -match '--port\s+(\d+)') { return [int]$Matches[1] }
29
+ }
30
+ return $null
31
+ }
32
+
33
+ # ---- 探测: 实际数据目录 (特征 = settings.yaml; 环境变量优先, 其次默认 ~\.dsh) ----
34
+ function Find-DshHome {
35
+ if ($env:DSH_HOME -and (Test-Path (Join-Path $env:DSH_HOME 'settings.yaml'))) { return $env:DSH_HOME }
36
+ $def = Join-Path $env:USERPROFILE '.dsh'
37
+ if (Test-Path (Join-Path $def 'settings.yaml')) { return $def }
38
+ return $null
39
+ }
40
+
41
+ # ---- 交互确认 (Auto 或 stdin 不可用时直接采用探测值) ----
42
+ function Confirm-Value([string]$label, [string]$current) {
43
+ if ($Auto) { return $current }
44
+ try {
45
+ $answer = Read-Host "$label [$current]"
46
+ if (-not [string]::IsNullOrWhiteSpace($answer)) { return $answer.Trim() }
47
+ } catch { } # 非交互环境: Read-Host 失败 -> 采用探测值
48
+ return $current
49
+ }
50
+
51
+ # 1) 探测 dsh 安装根目录
52
+ if (-not $DshRoot) {
53
+ $candidates = @(
54
+ (Join-Path $pkgRoot '..\..'), # 包直接放在 dsh 安装根内: <根>\dist\dsh-tray-package\
55
+ (Join-Path $env:ProgramFiles 'dsh'),
56
+ (Join-Path $env:USERPROFILE 'dsh'),
57
+ (Join-Path $env:LOCALAPPDATA 'dsh')
58
+ )
59
+ foreach ($c in $candidates) {
60
+ $c = [System.IO.Path]::GetFullPath($c)
61
+ if (Test-Path (Join-Path $c 'dsh.cmd')) { $DshRoot = $c; break }
62
+ }
63
+ }
64
+ $dsdCmd = Join-Path $DshRoot 'dsh.cmd'
65
+ if (-not (Test-Path $dsdCmd)) { throw "未找到 dsh 安装目录 (缺少 dsh.cmd: $dsdCmd)。请用 -DshRoot 指定。" }
66
+ Write-Host "[1/7] dsh 根目录: $DshRoot" -ForegroundColor Green
67
+
68
+ # 2) 探测 pwsh (PowerShell 7)
69
+ if (-not $PwshPath) {
70
+ $cmd = Get-Command pwsh -ErrorAction SilentlyContinue
71
+ if ($cmd) { $PwshPath = $cmd.Source }
72
+ elseif (Test-Path (Join-Path $env:ProgramFiles 'PowerShell\7\pwsh.exe')) { $PwshPath = Join-Path $env:ProgramFiles 'PowerShell\7\pwsh.exe' }
73
+ }
74
+ if (-not $PwshPath -or -not (Test-Path $PwshPath)) { throw "未找到 pwsh (PowerShell 7)。请用 -PwshPath 指定。" }
75
+ # 版本校验: 托盘常驻进程依赖 PowerShell 7.x (5.1 只能降级运行)
76
+ $pwshVer = & $PwshPath -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' 2>$null
77
+ if ($pwshVer -notmatch '^7\.') { throw "pwsh 必须是 PowerShell 7.x (检测到: $pwshVer)。请安装 PowerShell 7 或用 -PwshPath 指定。" }
78
+ Write-Host "[2/7] pwsh: $PwshPath (v$pwshVer)" -ForegroundColor Green
79
+
80
+ # 3) 探测 node
81
+ if (-not $NodePath) {
82
+ $cmd = Get-Command node -ErrorAction SilentlyContinue
83
+ if ($cmd) { $NodePath = $cmd.Source }
84
+ elseif (Test-Path 'C:\Program Files\nodejs\node.exe') { $NodePath = 'C:\Program Files\nodejs\node.exe' }
85
+ }
86
+ if (-not $NodePath -or -not (Test-Path $NodePath)) { throw "未找到 node.exe。请用 -NodePath 指定。" }
87
+ # 版本校验: 需要 ≥ 18 (dsh 本体运行要求)
88
+ $nodeVer = & $NodePath -v 2>$null
89
+ if ($nodeVer -notmatch '^v(\d+)\.') { throw "无法识别 node 版本 (检测到: $nodeVer)。请用 -NodePath 指定有效 node.exe。" }
90
+ if ([int]$Matches[1] -lt 18) { throw "node 必须 ≥ 18 (检测到: $nodeVer)。请升级 Node.js 或用 -NodePath 指定。" }
91
+ Write-Host "[3/7] node: $NodePath (v$($Matches[1]))" -ForegroundColor Green
92
+
93
+ # 4) 探测 Web 端口 + 数据目录, 展示确认
94
+ if ($Port -eq 0) {
95
+ $probed = Find-WebPort
96
+ if ($probed) { $Port = $probed }
97
+ }
98
+ if (-not $DshHome) {
99
+ $probed = Find-DshHome
100
+ if ($probed) { $DshHome = $probed }
101
+ }
102
+ if ($Port -eq 0) { $Port = 3088; Write-Host '警告: 未检测到运行中的 web 进程, 端口取包默认 3088 (如不符请用 -Port 指定)' -ForegroundColor Yellow }
103
+ if (-not $DshHome) { $DshHome = Join-Path $env:USERPROFILE '.dsh'; Write-Host "警告: 未探测到数据目录 (无 settings.yaml), 取默认 $DshHome (如不符请用 -DshHome 指定)" -ForegroundColor Yellow }
104
+ $Port = [int](Confirm-Value 'Web 端口' "$Port")
105
+ $DshHome = Confirm-Value '数据目录' $DshHome
106
+ Write-Host "[4/7] Web 端口: $Port ; 数据目录: $DshHome" -ForegroundColor Green
107
+
108
+ # 5) 安装到 ~\.dsh\launcher
109
+ New-Item -ItemType Directory -Force -Path $dst | Out-Null
110
+ Copy-Item -Path (Join-Path $pkgRoot 'launcher\*') -Destination $dst -Recurse -Force
111
+ Write-Host ('[5/7] 已安装到 ' + $dst) -ForegroundColor Green
112
+
113
+ # 6) 回填全部读取点 + 自动创建日志目录
114
+ $lib = Join-Path $dst 'dsh-lib.ps1'
115
+ $tray = Join-Path $dst 'dsh-tray.ps1'
116
+ $bin = Join-Path $DshRoot 'node_modules\@deepseek-ai\dsh\lib\bin.js'
117
+ if (-not (Test-Path $bin)) { Write-Host "警告: 未找到 $bin (dsh 包结构可能不同), 请检查 dsh-lib.ps1 的 \$BIN" -ForegroundColor Yellow }
118
+ $map = [ordered]@{
119
+ '$DSH_CMD = ' = "`$DSH_CMD = '$($DshRoot -replace "'", "''")\dsh.cmd'"
120
+ '$NODE = ' = "`$NODE = '$($NodePath -replace "'", "''")'"
121
+ '$BIN = ' = "`$BIN = '$($bin -replace "'", "''")'"
122
+ '$WORKDIR = ' = "`$WORKDIR = '$($DshRoot -replace "'", "''")'"
123
+ '$PORT = ' = "`$PORT = $Port"
124
+ '$PWSH = ' = "`$PWSH = '$($PwshPath -replace "'", "''")'"
125
+ }
126
+ $lines = Get-Content $lib
127
+ for ($i = 0; $i -lt $lines.Count; $i++) {
128
+ foreach ($k in $map.Keys) {
129
+ if ($lines[$i].StartsWith($k)) { $lines[$i] = $map[$k]; break }
130
+ }
131
+ }
132
+ # DSH_HOME: 先清旧行, 非默认数据目录时写入 (默认 ~\.dsh 不设 = dsh 原生行为)
133
+ $lines = @($lines | Where-Object { $_ -notmatch '^\$env:DSH_HOME\s*=' })
134
+ $defaultHome = Join-Path $env:USERPROFILE '.dsh'
135
+ if ($DshHome -ne $defaultHome) {
136
+ $lines = @($lines | ForEach-Object {
137
+ if ($_ -match '^\$WORKDIR') { $_; "`$env:DSH_HOME = '$($DshHome -replace "'", "''")' # 数据目录 (install 回填)" }
138
+ else { $_ }
139
+ })
140
+ }
141
+ Set-Content -Path $lib -Value $lines -Encoding utf8
142
+ # dsh-tray.ps1 param 默认值 (点源 lib 后 $PORT = $Port 会把 lib 改好的端口覆盖回去, 必须两处一致)
143
+ # 注意: param 块是多行格式 (param( 换行 [int]$Port = …), 只匹配参数行本身
144
+ $trayText = Get-Content $tray -Raw
145
+ $trayText = $trayText -replace '\[int\]\$Port = \d+', "[int]`$Port = $Port"
146
+ Set-Content -Path $tray -Value $trayText -Encoding utf8
147
+ # 自动创建日志目录 (缺失时 Add-Content -ErrorAction SilentlyContinue 会静默丢日志, 零排查线索)
148
+ New-Item -ItemType Directory -Force -Path $logDir | Out-Null
149
+ Write-Host "[6/7] 已回填 (端口 $Port ×2处 + DSH_HOME) + 日志目录就绪" -ForegroundColor Green
150
+
151
+ # 快捷方式: 开机自启 + 桌面 + 开始菜单(通知身份)
152
+ if (-not $NoShortcuts) {
153
+ $ws = New-Object -ComObject WScript.Shell
154
+ $icon = Join-Path $dst 'icons\dsh-tray-running.ico,0'
155
+ $argsLine = '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + (Join-Path $dst 'dsh-tray.ps1') + '"'
156
+ foreach ($target in @(
157
+ @{ Dir = [Environment]::GetFolderPath('Startup'); Name = 'DSH 系统托盘.lnk'; Desc = 'DSH 系统托盘 - 鲸鱼图标=运行状态, 右键控制' },
158
+ @{ Dir = [Environment]::GetFolderPath('Desktop'); Name = 'DSH 系统托盘.lnk'; Desc = 'DSH 系统托盘 - 双击唤起' },
159
+ @{ Dir = [Environment]::GetFolderPath('Programs'); Name = 'DSH 系统托盘.lnk'; Desc = 'DSH 系统托盘' }
160
+ )) {
161
+ $lnk = $ws.CreateShortcut((Join-Path $target.Dir $target.Name))
162
+ $lnk.TargetPath = $PwshPath
163
+ $lnk.Arguments = $argsLine
164
+ $lnk.WorkingDirectory = $dst
165
+ $lnk.IconLocation = $icon
166
+ $lnk.WindowStyle = 7
167
+ $lnk.Description = $target.Desc
168
+ $lnk.Save()
169
+ # v1.0.3: 给 .lnk 写 System.AppUserModel.ID —— 气泡通知归属「DSH 系统托盘」而非 PowerShell
170
+ if (-not ('DSH.Shortcut.Helper' -as [type])) {
171
+ Add-Type -TypeDefinition @'
172
+ using System;
173
+ using System.Runtime.InteropServices;
174
+ namespace DSH.Shortcut {
175
+ [StructLayout(LayoutKind.Sequential)]
176
+ public struct PropertyKey {
177
+ public Guid fmtid; public uint pid;
178
+ public PropertyKey(Guid g, uint p) { fmtid = g; pid = p; }
179
+ }
180
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
181
+ public struct PropVariant {
182
+ public short vt; public short r1; public short r2; public short r3;
183
+ public IntPtr ptr; public int x1; public int x2;
184
+ }
185
+ [ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
186
+ public interface IPropertyStore {
187
+ void GetCount(out uint c); void GetAt(uint i, out PropertyKey k);
188
+ void GetValue(ref PropertyKey k, out PropVariant v);
189
+ void SetValue(ref PropertyKey k, ref PropVariant v); void Commit();
190
+ }
191
+ public static class Helper {
192
+ [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
193
+ private static extern void SHGetPropertyStoreFromParsingName(string path, IntPtr zero, uint flags, ref Guid iid, [MarshalAs(UnmanagedType.Interface)] out IPropertyStore store);
194
+ public static void SetAumid(string lnkPath, string aumid) {
195
+ var key = new PropertyKey(new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 5); // System.AppUserModel.ID
196
+ var pv = new PropVariant(); pv.vt = 31; pv.ptr = Marshal.StringToCoTaskMemUni(aumid); // VT_LPWSTR
197
+ try {
198
+ Guid iid = typeof(IPropertyStore).GUID; IPropertyStore store;
199
+ SHGetPropertyStoreFromParsingName(lnkPath, IntPtr.Zero, 2, ref iid, out store); // GPS_READWRITE
200
+ store.SetValue(ref key, ref pv); store.Commit();
201
+ } finally { Marshal.FreeCoTaskMem(pv.ptr); }
202
+ }
203
+ }
204
+ }
205
+ '@
206
+ }
207
+ [DSH.Shortcut.Helper]::SetAumid((Join-Path $target.Dir $target.Name), 'DSH.WindowsTray')
208
+ }
209
+ # v1.0.3: 预授权通知(Win11 升级会清应用通知授权, 提前放行 DSH.WindowsTray)
210
+ $nsKey = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\DSH.WindowsTray'
211
+ New-Item -Path $nsKey -Force | Out-Null
212
+ Set-ItemProperty -Path $nsKey -Name 'Enabled' -Value 1 -Type DWord
213
+ Set-ItemProperty -Path $nsKey -Name 'ShowInActionCenter' -Value 1 -Type DWord
214
+ Write-Host '快捷方式: 开机自启 + 桌面 + 开始菜单 已创建 (AUMID=DSH.WindowsTray, 通知已预授权)' -ForegroundColor Green
215
+ }
216
+
217
+ # 启动托盘
218
+ if (-not $NoTrayStart) {
219
+ Start-Process -FilePath $PwshPath -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-WindowStyle', 'Hidden', '-File', (Join-Path $dst 'dsh-tray.ps1')) -WindowStyle Hidden
220
+ Write-Host '托盘已启动 (若图标在 ^ 溢出区, 拖到任务栏即可)' -ForegroundColor Green
221
+ }
222
+
223
+ Write-Host ''
224
+ Write-Host '部署完成! 常用命令:' -ForegroundColor Cyan
225
+ Write-Host " 手动唤起托盘: 桌面「DSH 系统托盘」/ 控制台选项 5"
226
+ Write-Host ' 取消自启: 删除启动文件夹里的「DSH 系统托盘.lnk」'
227
+ Write-Host ' 卸载: 删除快捷方式 + 删除 ~\.dsh\launcher'
228
+ Write-Host " 验证: $logDir\dsh-tray.log 首行「托盘启动 (…端口 $Port)」" -ForegroundColor Cyan
@@ -1,50 +1,50 @@
1
- # dsh-actions.ps1 —— 托盘菜单动作执行器 (由 dsh-tray.ps1 以独立隐藏进程拉起)
2
- # 用法: pwsh -NoProfile -File dsh-actions.ps1 -Action start|stop|restart|tui-start|tui-stop
3
- # 动作在独立进程执行, 托盘 UI 线程保持响应; 日志写入 ~/.dsh/logs/dsh-tray-actions.log
4
- param(
5
- [Parameter(Mandatory = $true)]
6
- [ValidateSet('start', 'stop', 'restart', 'tui-start', 'tui-stop')]
7
- [string]$Action
8
- )
9
- $ErrorActionPreference = 'Stop'
10
-
11
- . (Join-Path $PSScriptRoot 'dsh-lib.ps1')
12
-
13
- $logFile = Join-Path $LOG_DIR 'dsh-tray-actions.log'
14
- function Log([string]$msg) {
15
- $line = ('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg)
16
- Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue
17
- }
18
-
19
- try {
20
- switch ($Action) {
21
- 'start' {
22
- Log '启动 DSH (来自系统托盘)'
23
- Start-Dsh -OpenBrowser:$false
24
- Log '启动动作完成'
25
- }
26
- 'stop' {
27
- Log '停止 DSH (来自系统托盘)'
28
- Stop-Dsh
29
- Log '停止动作完成'
30
- }
31
- 'restart' {
32
- Log '重启 DSH (来自系统托盘)'
33
- Restart-Dsh
34
- Log '重启动作完成'
35
- }
36
- 'tui-start' {
37
- Log '启动 TUI (来自系统托盘)'
38
- Start-Tui
39
- Log 'TUI 启动动作完成'
40
- }
41
- 'tui-stop' {
42
- Log '停止 TUI (来自系统托盘)'
43
- Stop-Tui
44
- Log 'TUI 停止动作完成'
45
- }
46
- }
47
- } catch {
48
- Log ("动作 {0} 失败: {1}" -f $Action, $_.Exception.Message)
49
- exit 1
50
- }
1
+ # dsh-actions.ps1 —— 托盘菜单动作执行器 (由 dsh-tray.ps1 以独立隐藏进程拉起)
2
+ # 用法: pwsh -NoProfile -File dsh-actions.ps1 -Action start|stop|restart|tui-start|tui-stop
3
+ # 动作在独立进程执行, 托盘 UI 线程保持响应; 日志写入 ~/.dsh/logs/dsh-tray-actions.log
4
+ param(
5
+ [Parameter(Mandatory = $true)]
6
+ [ValidateSet('start', 'stop', 'restart', 'tui-start', 'tui-stop')]
7
+ [string]$Action
8
+ )
9
+ $ErrorActionPreference = 'Stop'
10
+
11
+ . (Join-Path $PSScriptRoot 'dsh-lib.ps1')
12
+
13
+ $logFile = Join-Path $LOG_DIR 'dsh-tray-actions.log'
14
+ function Log([string]$msg) {
15
+ $line = ('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg)
16
+ Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue
17
+ }
18
+
19
+ try {
20
+ switch ($Action) {
21
+ 'start' {
22
+ Log '启动 DSH (来自系统托盘)'
23
+ Start-Dsh -OpenBrowser:$false
24
+ Log '启动动作完成'
25
+ }
26
+ 'stop' {
27
+ Log '停止 DSH (来自系统托盘)'
28
+ Stop-Dsh
29
+ Log '停止动作完成'
30
+ }
31
+ 'restart' {
32
+ Log '重启 DSH (来自系统托盘)'
33
+ Restart-Dsh
34
+ Log '重启动作完成'
35
+ }
36
+ 'tui-start' {
37
+ Log '启动 TUI (来自系统托盘)'
38
+ Start-Tui
39
+ Log 'TUI 启动动作完成'
40
+ }
41
+ 'tui-stop' {
42
+ Log '停止 TUI (来自系统托盘)'
43
+ Stop-Tui
44
+ Log 'TUI 停止动作完成'
45
+ }
46
+ }
47
+ } catch {
48
+ Log ("动作 {0} 失败: {1}" -f $Action, $_.Exception.Message)
49
+ exit 1
50
+ }
@@ -1,40 +1,40 @@
1
- # DSH 控制台 —— 启动/重启 Web + 启动 TUI + 状态查看 + 系统托盘
2
- # 部署:2026-08-26;双击桌面「DSH 控制台」或 dsh-control.bat
3
- # 坑:本脚本由用户独立窗口运行(非 web 子进程),杀/重启 web 安全(PITFALLS §12.2)
4
- # 2026-08-28: 启动/停止/重启/托盘逻辑抽到 dsh-lib.ps1(与系统托盘共享同一份代码)
5
- $ErrorActionPreference = 'Stop'
6
- chcp 65001 > $null
7
-
8
- . (Join-Path $PSScriptRoot 'dsh-lib.ps1')
9
-
10
- while ($true) {
11
- Clear-Host
12
- Write-Host ''
13
- Write-Host ' ┌──────────────────────────────┐'
14
- Write-Host ' │ DSH 控制台 │'
15
- Write-Host ' ├──────────────────────────────┤'
16
- Write-Host ' │ 1) 启动 / 打开 Web (3088) │'
17
- Write-Host ' │ 2) 重启 Web │'
18
- Write-Host ' │ 3) 启动 TUI(新终端窗口) │'
19
- Write-Host ' │ 4) 查看状态 │'
20
- Write-Host ' │ 5) 启动系统托盘 │'
21
- Write-Host ' │ 0) 退出 │'
22
- Write-Host ' └──────────────────────────────┘'
23
- $c = Read-Host ' 请选择'
24
- switch ($c) {
25
- '1' { Start-Dsh }
26
- '2' { Restart-Dsh }
27
- '3' { Start-Tui }
28
- '4' { Show-Status }
29
- '5' {
30
- if (Ensure-DshTray) {
31
- Write-Host '[tray] 系统托盘已在运行(蓝鲸=运行中 / 灰鲸=未运行,右键可控制)' -ForegroundColor Green
32
- } else {
33
- Write-Host '[tray] 系统托盘已启动(蓝鲸=运行中 / 灰鲸=未运行,右键可控制)' -ForegroundColor Green
34
- }
35
- }
36
- '0' { Write-Host ' 再见!'; break }
37
- default { Write-Host " 无效选择: $c" -ForegroundColor Red }
38
- }
39
- if ($c -ne '0') { Write-Host ''; Read-Host ' 按回车返回菜单' }
40
- }
1
+ # DSH 控制台 —— 启动/重启 Web + 启动 TUI + 状态查看 + 系统托盘
2
+ # 部署:2026-08-26;双击桌面「DSH 控制台」或 dsh-control.bat
3
+ # 坑:本脚本由用户独立窗口运行(非 web 子进程),杀/重启 web 安全(PITFALLS §12.2)
4
+ # 2026-08-28: 启动/停止/重启/托盘逻辑抽到 dsh-lib.ps1(与系统托盘共享同一份代码)
5
+ $ErrorActionPreference = 'Stop'
6
+ chcp 65001 > $null
7
+
8
+ . (Join-Path $PSScriptRoot 'dsh-lib.ps1')
9
+
10
+ while ($true) {
11
+ Clear-Host
12
+ Write-Host ''
13
+ Write-Host ' ┌──────────────────────────────┐'
14
+ Write-Host ' │ DSH 控制台 │'
15
+ Write-Host ' ├──────────────────────────────┤'
16
+ Write-Host ' │ 1) 启动 / 打开 Web (3088) │'
17
+ Write-Host ' │ 2) 重启 Web │'
18
+ Write-Host ' │ 3) 启动 TUI(新终端窗口) │'
19
+ Write-Host ' │ 4) 查看状态 │'
20
+ Write-Host ' │ 5) 启动系统托盘 │'
21
+ Write-Host ' │ 0) 退出 │'
22
+ Write-Host ' └──────────────────────────────┘'
23
+ $c = Read-Host ' 请选择'
24
+ switch ($c) {
25
+ '1' { Start-Dsh }
26
+ '2' { Restart-Dsh }
27
+ '3' { Start-Tui }
28
+ '4' { Show-Status }
29
+ '5' {
30
+ if (Ensure-DshTray) {
31
+ Write-Host '[tray] 系统托盘已在运行(蓝鲸=运行中 / 灰鲸=未运行,右键可控制)' -ForegroundColor Green
32
+ } else {
33
+ Write-Host '[tray] 系统托盘已启动(蓝鲸=运行中 / 灰鲸=未运行,右键可控制)' -ForegroundColor Green
34
+ }
35
+ }
36
+ '0' { Write-Host ' 再见!'; break }
37
+ default { Write-Host " 无效选择: $c" -ForegroundColor Red }
38
+ }
39
+ if ($c -ne '0') { Write-Host ''; Read-Host ' 按回车返回菜单' }
40
+ }