@@ -33,16 +33,50 @@ $DefaultRegistry = [System.Text.Encoding]::UTF8.GetString([System.Convert]::From
3333# ---------------------------
3434# Helpers
3535# ---------------------------
36- function Info ([string ]$msg ) { Write-Host $msg }
37- function Warn ([string ]$msg ) { Write-Host $msg }
38- function Fail ([string ]$msg ) { Write-Host $msg }
36+ function Info ([string ]$msg ) { Write-Host " $msg " - ForegroundColor Cyan }
37+ function Warn ([string ]$msg ) { Write-Host " ⚠ $msg " - ForegroundColor Yellow }
38+ function Fail ([string ]$msg ) { Write-Host " ✖ $msg " - ForegroundColor Red }
39+ function Success ([string ]$msg ) { Write-Host " ✔ $msg " - ForegroundColor Green }
40+ function Dim ([string ]$msg ) { Write-Host " $msg " - ForegroundColor DarkGray }
41+
42+ function Show-Banner {
43+ $lines = @ (
44+ " ____ ____ _ _ _ "
45+ " | _ \ ___ ___ _ __ / ___|| |_ _ _ __| (_) ___ "
46+ " | | | |/ _ \/ _ \ '_ \\___ \| __| | | |/ _`` | |/ _ \ "
47+ " | |_| | __/ __/ |_) |___) | |_| |_| | (_| | | (_) |"
48+ " |____/ \___|\___| .__/|____/ \__|\__,_|\__,_|_|\___/ "
49+ " |_| I n s t a l l e r v2 "
50+ )
51+ $colors = @ (" Red" , " Yellow" , " Green" , " Cyan" , " Blue" , " Magenta" )
52+ Write-Host " "
53+ for ($i = 0 ; $i -lt $lines.Count ; $i ++ ) {
54+ Write-Host $lines [$i ] - ForegroundColor $colors [$i % $colors.Count ]
55+ }
56+ Write-Host " "
57+ }
58+
59+ function Mask-Url ([string ]$url ) {
60+ if ([string ]::IsNullOrEmpty($url )) { return " <empty>" }
61+ try {
62+ $u = [Uri ]$url
63+ $host_ = $u.Host
64+ if ($host_.Length -gt 8 ) {
65+ $host_ = $host_.Substring (0 , 4 ) + " ****" + $host_.Substring ($host_.Length - 4 )
66+ }
67+ return " $ ( $u.Scheme ) ://$host_ /****"
68+ } catch {
69+ if ($url.Length -le 10 ) { return (" *" * $url.Length ) }
70+ return $url.Substring (0 , 5 ) + " ****" + $url.Substring ($url.Length - 4 )
71+ }
72+ }
3973
4074function Require-Npm {
4175 if (-not (Get-Command npm - ErrorAction SilentlyContinue)) {
4276 Write-Host " "
43- Write-Host " ❌ Node.js / npm not found."
44- Write-Host " Please install Node.js first:"
45- Write-Host " https://nodejs.org/en/download"
77+ Fail " Node.js / npm not found."
78+ Info " Please install Node.js first:"
79+ Info " 👉 https://nodejs.org/en/download"
4680 Write-Host " "
4781 exit 1
4882 }
@@ -153,34 +187,104 @@ function Run-NpmViaCmd([string]$cmdLine) {
153187 }
154188}
155189
190+ # ---------------------------
191+ # Azure CLI installation
192+ # ---------------------------
193+ function Install-AzureCli {
194+ # Attempt to install Azure CLI via winget
195+ if (-not (Get-Command winget - ErrorAction SilentlyContinue)) {
196+ Warn " winget not found — cannot auto-install Azure CLI."
197+ return $false
198+ }
199+
200+ Info " 📥 Installing Azure CLI via winget..."
201+ Write-Host " "
202+ try {
203+ & winget install -- id Microsoft.AzureCLI -- accept- source- agreements -- accept- package- agreements
204+ if ($LASTEXITCODE -ne 0 ) {
205+ Warn " winget install returned exit code $LASTEXITCODE ."
206+ return $false
207+ }
208+ # Refresh PATH so az is available in the current session
209+ $env: PATH = [System.Environment ]::GetEnvironmentVariable(" PATH" , " Machine" ) + " ;" + [System.Environment ]::GetEnvironmentVariable(" PATH" , " User" )
210+ if (Get-Command az - ErrorAction SilentlyContinue) {
211+ Success " Azure CLI installed successfully."
212+ return $true
213+ } else {
214+ Warn " Azure CLI installed but 'az' not found in PATH. You may need to restart your terminal."
215+ return $false
216+ }
217+ }
218+ catch {
219+ Warn " Failed to install Azure CLI: $ ( $_.Exception.Message ) "
220+ return $false
221+ }
222+ }
223+
156224# ---------------------------
157225# Azure CLI token acquisition
158226# ---------------------------
159227function Get-AzAccessToken {
160228 # Try to obtain a temporary access token via Azure CLI for Azure DevOps.
161229 # The resource ID 499b84ac-1321-427f-aa17-267ca6975798 is the well-known
162230 # resource identifier for Azure DevOps (Azure Artifacts / Packaging).
163- if (-not (Get-Command az - ErrorAction SilentlyContinue)) {
164- Info " Azure CLI (az) not found. Will fall back to manual PAT entry."
231+ $azAvailable = [bool ](Get-Command az - ErrorAction SilentlyContinue)
232+
233+ if (-not $azAvailable ) {
234+ Write-Host " "
235+ Write-Host " ┌─────────────────────────────────────────────────────┐" - ForegroundColor Yellow
236+ Write-Host " │ 🔧 Azure CLI (az) is not installed │" - ForegroundColor Yellow
237+ Write-Host " │ It is recommended for automatic token auth. │" - ForegroundColor Yellow
238+ Write-Host " └─────────────────────────────────────────────────────┘" - ForegroundColor Yellow
239+ Write-Host " "
240+ Write-Host " 📥 " - NoNewline - ForegroundColor Cyan
241+ $installChoice = Read-Host " Install Azure CLI now via winget? [Y/n]"
242+ if ([string ]::IsNullOrWhiteSpace($installChoice ) -or $installChoice -match ' ^[Yy]' ) {
243+ $installed = Install-AzureCli
244+ if ($installed ) {
245+ $azAvailable = $true
246+ } else {
247+ Dim " Continuing without Azure CLI."
248+ }
249+ } else {
250+ Dim " Skipping Azure CLI installation."
251+ }
252+ }
253+
254+ if (-not $azAvailable ) {
255+ Dim " Will fall back to manual PAT entry."
165256 return $null
166257 }
167258
168- Info " Azure CLI found. Checking for existing az login session..."
259+ Info " 🔍 Azure CLI found. Checking login session..."
169260
170261 try {
171262 $tokenJson = & az account get-access - token -- resource " 499b84ac-1321-427f-aa17-267ca6975798" -- query " accessToken" - o tsv 2>&1
172263 if ($LASTEXITCODE -ne 0 -or [string ]::IsNullOrWhiteSpace($tokenJson )) {
173- if ($VerboseInstall -and $tokenJson ) { Info " az output: $tokenJson " }
174- Info " No valid az login session found. Will fall back to manual PAT entry."
175- return $null
264+ if ($VerboseInstall -and $tokenJson ) { Dim " az output: $tokenJson " }
265+ Warn " No valid az login session found."
266+ Write-Host " "
267+ Info " 🔐 Please log in to Azure to continue..."
268+ Write-Host " "
269+ & az login
270+ if ($LASTEXITCODE -ne 0 ) {
271+ Warn " az login failed — will fall back to manual PAT entry."
272+ return $null
273+ }
274+ # Retry after login
275+ $tokenJson = & az account get-access - token -- resource " 499b84ac-1321-427f-aa17-267ca6975798" -- query " accessToken" - o tsv 2>&1
276+ if ($LASTEXITCODE -ne 0 -or [string ]::IsNullOrWhiteSpace($tokenJson )) {
277+ Warn " Still unable to get token after login — will fall back to manual PAT entry."
278+ return $null
279+ }
176280 }
177281 $token = $tokenJson.Trim ()
178- Info " ✅ Obtained temporary access token from Azure CLI session ."
282+ Success " Obtained temporary access token from Azure CLI."
179283 return $token
180284 }
181285 catch {
182- Info " Failed to get token from Azure CLI: $ ( $_.Exception.Message ) "
183- Info " Will fall back to manual PAT entry."
286+ Warn " Failed to get token from Azure CLI: $ ( $_.Exception.Message ) "
287+ Dim " Will fall back to manual PAT entry."
184288 return $null
185289 }
186290}
@@ -190,23 +294,39 @@ function Get-AzAccessToken {
190294# ---------------------------
191295Require- Npm
192296
193- Info " "
194- Info " === DeepStudio npm installer (v2) ==="
195- Info " Package: $PackageName @latest"
196- Info (" VerboseInstall: " + $ (if ($VerboseInstall ) { " ON" } else { " OFF" }))
197- Info (" DryRun: " + $ (if ($DryRun ) { " ON" } else { " OFF" }))
198- Info (" LogToFile: " + $ (if ($EnableLog ) { " ON ($LogPath )" } else { " OFF" }))
199- Info " "
200-
201- # Get registry: env > default
297+ Show-Banner
298+
299+ Write-Host " ┌─────────────────────────────────────────┐" - ForegroundColor DarkCyan
300+ Write-Host " │ 📦 Package: " - NoNewline - ForegroundColor DarkCyan
301+ Write-Host " $PackageName @latest" - NoNewline - ForegroundColor White
302+ Write-Host " │" - ForegroundColor DarkCyan
303+ Write-Host " │ 🔧 Verbose: " - NoNewline - ForegroundColor DarkCyan
304+ Write-Host $ (if ($VerboseInstall ) { " ON " } else { " OFF" }) - NoNewline - ForegroundColor $ (if ($VerboseInstall ) { " Green" } else { " DarkGray" })
305+ Write-Host " │" - ForegroundColor DarkCyan
306+ Write-Host " │ 🧪 DryRun: " - NoNewline - ForegroundColor DarkCyan
307+ Write-Host $ (if ($DryRun ) { " ON " } else { " OFF" }) - NoNewline - ForegroundColor $ (if ($DryRun ) { " Yellow" } else { " DarkGray" })
308+ Write-Host " │" - ForegroundColor DarkCyan
309+ Write-Host " │ 📝 LogFile: " - NoNewline - ForegroundColor DarkCyan
310+ Write-Host $ (if ($EnableLog ) { " ON " } else { " OFF" }) - NoNewline - ForegroundColor $ (if ($EnableLog ) { " Green" } else { " DarkGray" })
311+ Write-Host " │" - ForegroundColor DarkCyan
312+ Write-Host " └─────────────────────────────────────────┘" - ForegroundColor DarkCyan
313+ Write-Host " "
314+
315+ # Get registry: env > construct from org name using default template
202316$registryInput = $RegistryFromEnv
203317if ([string ]::IsNullOrWhiteSpace($registryInput )) {
204- $registryInput = $DefaultRegistry
318+ Write-Host " 🏢 " - NoNewline - ForegroundColor Cyan
319+ $adoOrg = Read-Host " Enter ADO org name [microsoft]"
320+ if ([string ]::IsNullOrWhiteSpace($adoOrg )) { $adoOrg = " microsoft" }
321+ # Construct registry URL from org name using the base64 template
322+ # Template: https://{org}.pkgs.visualstudio.com/OS/_packaging/DeepStudio/npm/registry/
323+ $registryInput = $DefaultRegistry -replace " microsoft\.pkgs" , " $adoOrg .pkgs"
324+ Dim " Using org: $adoOrg "
205325}
206326$registry = Ensure- Registry $registryInput
207327
208- Info " Registry: $registry "
209- Info " "
328+ Dim " Registry: $ ( Mask - Url $ registry) "
329+ Write-Host " "
210330
211331# derive auth prefix (npmrc style)
212332$uri = [Uri ]$registry
@@ -216,24 +336,27 @@ $authPrefix = "//" + $uri.Host + $uri.AbsolutePath.TrimEnd("/") + "/"
216336$pat = Get-AzAccessToken
217337
218338if ([string ]::IsNullOrWhiteSpace($pat )) {
219- Info " "
220- Info " Manual PAT required. You can create one at:"
221- Info " https://dev.azure.com/ > User Settings > Personal Access Tokens"
222- Info " Scope: Packaging > Read"
223- Info " "
224-
225- $patRaw = Read-Secure " Enter Azure DevOps PAT (Packaging:Read)"
339+ Write-Host " "
340+ Write-Host " ┌─────────────────────────────────────────────────────┐" - ForegroundColor Yellow
341+ Write-Host " │ 🔑 Manual PAT required │" - ForegroundColor Yellow
342+ Write-Host " │ Create one at: │" - ForegroundColor Yellow
343+ Write-Host " │ https://dev.azure.com/ > User Settings > PATs │" - ForegroundColor Yellow
344+ Write-Host " │ Scope: Packaging > Read │" - ForegroundColor Yellow
345+ Write-Host " └─────────────────────────────────────────────────────┘" - ForegroundColor Yellow
346+ Write-Host " "
347+
348+ $patRaw = Read-Secure " 🔑 Enter Azure DevOps PAT (Packaging:Read)"
226349 if ([string ]::IsNullOrWhiteSpace($patRaw )) { throw " PAT is empty." }
227350
228351 $pat = $patRaw.Trim ()
229352 $patRaw = $null
230353} else {
231- Info " Using temporary token from Azure CLI (no PAT creation needed)."
354+ Success " Using temporary token from Azure CLI (no PAT creation needed)."
232355}
233356
234- Info " "
235- Info (" Token (masked): " + (Mask- Token $pat ))
236- Info " "
357+ Write-Host " "
358+ Dim (" Token (masked): " + (Mask- Token $pat ))
359+ Write-Host " "
237360
238361# npm install args / loglevel
239362$logLevel = if ($VerboseInstall ) { " verbose" } else { " notice" }
@@ -248,16 +371,19 @@ if ($VerboseInstall) {
248371}
249372
250373try {
251- Info " Preparing isolated npm config (ignores your existing .npmrc) ..."
374+ Info " 📄 Preparing isolated npm config..."
252375 Write-IsolatedNpmrc - path $tmpNpmrc - registry $registry - authPrefix $authPrefix - pat $pat
253376
254377 # Build command line (use cmd.exe to avoid PowerShell alias/function issues with npm)
255378 # IMPORTANT: Use --userconfig so npm only reads our isolated temp npmrc for this run.
256379 $installCmd = ' npm install -g "{0}@latest" --registry "{1}/" --loglevel {2} --userconfig "{3}"' -f $PackageName , $registry , $logLevel , $tmpNpmrc
257380
258- Info " "
259- Info (" Command: " + $installCmd )
260- Info " "
381+ if ($VerboseInstall ) {
382+ Dim (" Command: " + $installCmd )
383+ }
384+ Write-Host " "
385+ Info " 📦 Installing $PackageName @latest ..."
386+ Write-Host " "
261387
262388 Run- NpmViaCmd $installCmd
263389
@@ -267,53 +393,60 @@ try {
267393 Run- NpmViaCmd $verifyCmd
268394 }
269395
270- Info " "
271- Info " ✅ Installed $PackageName @latest successfully."
272- Info " "
396+ Write-Host " "
397+ Write-Host " ┌─────────────────────────────────────────────────┐" - ForegroundColor Green
398+ Write-Host " │ 🎉 $PackageName @latest installed successfully! │" - ForegroundColor Green
399+ Write-Host " └─────────────────────────────────────────────────┘" - ForegroundColor Green
400+ Write-Host " "
273401
274402 # Ask user whether to start deepstudio-server
403+ Write-Host " 🚀 " - NoNewline - ForegroundColor Magenta
275404 $startChoice = Read-Host " Start $PackageName now? [Y/n]"
276405 if ([string ]::IsNullOrWhiteSpace($startChoice ) -or $startChoice -match ' ^[Yy]' ) {
277- Info " "
278- Info " 🚀 Launching $PackageName (press Ctrl+C to stop)..."
279- Info " "
406+ Write-Host " "
407+ Write-Host " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - ForegroundColor Magenta
408+ Success " Launching $PackageName (press Ctrl+C to stop)..."
409+ Write-Host " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - ForegroundColor Magenta
410+ Write-Host " "
280411 if ($DryRun ) {
281- Info " DRYRUN: would run $PackageName "
412+ Dim " DRYRUN: would run $PackageName "
282413 } else {
283414 & $PackageName
284415 }
285416 } else {
286- Info " "
417+ Write-Host " "
287418 Info " You can start it later by running: $PackageName "
288419 }
289420}
290421catch {
291- Info " "
292- Fail " ❌ Installation failed."
422+ Write-Host " "
423+ Write-Host " ┌─────────────────────────────────────────────────┐" - ForegroundColor Red
424+ Write-Host " │ ❌ Installation failed │" - ForegroundColor Red
425+ Write-Host " └─────────────────────────────────────────────────┘" - ForegroundColor Red
293426 Fail (" Error: " + $_.Exception.Message )
294427
295- Warn " "
428+ Write-Host " "
296429 Warn " Common causes for 401/403:"
297- Warn " - Azure CLI token expired (try: az login)"
298- Warn " - PAT missing Packaging:Read scope"
299- Warn " - PAT pasted with extra whitespace (we trimmed, but double-check the masked value) "
300- Warn " - Wrong registry URL (must be the npm/registry endpoint) "
301- Warn " - No permission to the Azure Artifacts feed"
302- Warn " - Corporate proxy/SSL interception issues"
430+ Dim " • Azure CLI token expired (try: az login)"
431+ Dim " • PAT missing Packaging:Read scope"
432+ Dim " • PAT pasted with extra whitespace"
433+ Dim " • Wrong registry URL"
434+ Dim " • No permission to the Azure Artifacts feed"
435+ Dim " • Corporate proxy/SSL interception issues"
303436
304437 if ($VerboseInstall ) {
305- Info " "
306- Info " ---- Full exception ----"
307- Info $_.Exception.ToString ()
308- Info " ------------------------"
438+ Write-Host " "
439+ Dim " ---- Full exception ----"
440+ Dim $_.Exception.ToString ()
441+ Dim " ------------------------"
309442 } else {
310- Warn " "
443+ Write-Host " "
311444 Warn " Tip: re-run with verbose:"
312- Warn " `$ env:DEEPSTUDIO_VERBOSE='1'; `$ env:DEEPSTUDIO_LOG='1'; irm <url> | iex"
445+ Dim " `$ env:DEEPSTUDIO_VERBOSE='1'; `$ env:DEEPSTUDIO_LOG='1'; irm <url> | iex"
313446 }
314447
315448 if ($EnableLog -and -not $DryRun ) {
316- Warn " "
449+ Write-Host " "
317450 Warn " Log saved to: $LogPath "
318451 }
319452
@@ -328,7 +461,7 @@ finally {
328461 Remove-Item Env:\NPM_CONFIG_LOGLEVEL - ErrorAction SilentlyContinue
329462 Remove-Item Env:\NPM_CONFIG_PROGRESS - ErrorAction SilentlyContinue
330463 }
331- Info " "
332- Info " ✅ Cleanup complete."
333- Info " "
464+ Write-Host " "
465+ Success " Cleanup complete."
466+ Write-Host " "
334467}
0 commit comments