11import * as vscode from "vscode"
22import { userInfo } from "os"
3+ import { terminalUnsupportedSyntax } from "./shellConstants"
34import fs from "fs"
45import * as path from "path"
6+ import { exec } from "child_process"
7+ import { promisify } from "util"
58
69// Security: Allowlist of approved shell executables to prevent arbitrary command execution
710const SHELL_ALLOWLIST = new Set < string > ( [
@@ -16,6 +19,7 @@ const SHELL_ALLOWLIST = new Set<string>([
1619
1720 // Windows WSL
1821 "C:\\Windows\\System32\\wsl.exe" ,
22+ "wsl.exe" ,
1923
2024 // Git Bash on Windows
2125 "C:\\Program Files\\Git\\bin\\bash.exe" ,
@@ -142,9 +146,15 @@ function getWindowsTerminalConfig() {
142146 const config = vscode . workspace . getConfiguration ( "terminal.integrated" )
143147 const defaultProfileName = config . get < string > ( "defaultProfile.windows" )
144148 const profiles = config . get < WindowsTerminalProfiles > ( "profiles.windows" ) || { }
145- return { defaultProfileName, profiles }
149+ return {
150+ defaultProfileName,
151+ profiles,
152+ }
146153 } catch {
147- return { defaultProfileName : null , profiles : { } as WindowsTerminalProfiles }
154+ return {
155+ defaultProfileName : null ,
156+ profiles : { } as WindowsTerminalProfiles ,
157+ }
148158 }
149159}
150160
@@ -203,7 +213,7 @@ function getWindowsShellFromVSCode(): string | null {
203213 if ( normalizedPath ) {
204214 // If there's an explicit PowerShell path, return that
205215 return normalizedPath
206- } else if ( profile ?. source === "PowerShell" ) {
216+ } else if ( profile ?. source === "PowerShell" && profile ?. path ) {
207217 // If the profile is sourced from PowerShell, assume the newest
208218 return SHELL_PATHS . POWERSHELL_7
209219 }
@@ -587,3 +597,233 @@ function detectLinuxVSCodeDefaultShell(): string | null {
587597export function getActiveTerminalShellType ( ) : string | null {
588598 return detectSystemAvailableShell ( )
589599}
600+
601+ interface TerminalUnsupportedSyntax {
602+ unsupported ?: string [ ]
603+ features ?: string [ ]
604+ }
605+
606+ export interface TerminalInfo {
607+ name : string
608+ version : string
609+ path : string
610+ unsupportSyntax ?: string [ ]
611+ features ?: string [ ]
612+ }
613+
614+ const execAsync = promisify ( exec )
615+
616+ /**
617+ * Get PowerShell version information by executing command
618+ * @param shellPath PowerShell path
619+ * @returns Version string or null
620+ */
621+ async function getPowerShellVersion ( shellPath : string ) : Promise < string | null > {
622+ try {
623+ // Execute PowerShell command to get version information
624+ const { stdout } = await execAsync ( `"${ shellPath } " -Command "$PSVersionTable"` )
625+ const getPSVersion = ( str : string ) => str . replace ( / \\ x 1 b \[ [ 0 - 9 ; ] * m / g, "" ) . match ( / P S V e r s i o n \s + ( [ 0 - 9 . ] + ) / )
626+ const versionMatch = getPSVersion ( stdout )
627+ if ( versionMatch ) {
628+ const major = versionMatch [ 1 ]
629+ // Try to get more detailed version information
630+ try {
631+ const { stdout : detailStdout } = await execAsync (
632+ `"${ shellPath } " -Command "$PSVersionTable.PSVersion.ToString()"` ,
633+ )
634+ return detailStdout . trim ( )
635+ } catch {
636+ return major
637+ }
638+ }
639+ return null
640+ } catch ( error ) {
641+ console . debug ( "[getPowerShellVersion] Failed to get PowerShell version:" , error )
642+ return null
643+ }
644+ }
645+
646+ /**
647+ * Get Git Bash version information by executing command
648+ */
649+ export async function getGitBashVersion ( ) : Promise < { path : string ; version : string } | { version : null } > {
650+ async function getBashPathsFromWhere ( ) : Promise < string [ ] > {
651+ try {
652+ const { stdout } = await execAsync ( "where bash" )
653+ return stdout
654+ . split ( / \r ? \n / )
655+ . map ( ( p ) => p . trim ( ) )
656+ . filter ( Boolean )
657+ } catch {
658+ return [ ]
659+ }
660+ }
661+
662+ async function getBashPathFromRegistry ( ) : Promise < string | null > {
663+ try {
664+ const { stdout } = await execAsync ( 'reg query "HKEY_LOCAL_MACHINE\\SOFTWARE\\GitForWindows" /v InstallPath' )
665+ const match = stdout . match ( / I n s t a l l P a t h \s + R E G _ S Z \s + ( .+ ) / )
666+ if ( match ) {
667+ return `${ match [ 1 ] } \\bin\\bash.exe`
668+ }
669+ } catch { }
670+ return null
671+ }
672+
673+ const fallbackPaths = [
674+ "C:\\Program Files\\Git\\bin\\bash.exe" ,
675+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe" ,
676+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe" ,
677+ ]
678+
679+ const paths = new Set < string > ( )
680+
681+ for ( const p of await getBashPathsFromWhere ( ) ) paths . add ( p )
682+ const regPath = await getBashPathFromRegistry ( )
683+ if ( regPath ) paths . add ( regPath )
684+ for ( const p of fallbackPaths ) paths . add ( p )
685+
686+ const validCandidates : { path : string ; version : string } [ ] = [ ]
687+
688+ for ( const path of paths ) {
689+ try {
690+ const { stdout } = await execAsync ( `"${ path } " --version` )
691+ const versionMatch = stdout . match ( / v e r s i o n \s + ( [ \d . ] + ) / i)
692+ if ( versionMatch ) {
693+ validCandidates . push ( { path, version : versionMatch [ 1 ] } )
694+ }
695+ } catch {
696+ continue
697+ }
698+ }
699+
700+ if ( validCandidates . length === 0 ) {
701+ console . debug ( "[getGitBashVersion] No valid Git Bash found." )
702+ return { version : null }
703+ }
704+
705+ // Prioritize the "Git for Windows" version
706+ const preferred = validCandidates . find ( ( c ) => c . path . toLowerCase ( ) . includes ( "program files\\git" ) )
707+ return preferred || validCandidates [ 0 ]
708+ }
709+
710+ /**
711+ * Get CMD version information by executing command
712+ * @param shellPath CMD path
713+ * @returns Version string or null
714+ */
715+ async function getCMDVersion ( shellPath : string ) : Promise < string | null > {
716+ try {
717+ // Execute ver command to get Windows version information
718+ const { stdout } = await execAsync ( `cmd /c ver` )
719+ const versionMatch = stdout . match ( / ( [ \d . ] + ) / )
720+ if ( versionMatch ) {
721+ return versionMatch [ 1 ]
722+ }
723+ return null
724+ } catch ( error ) {
725+ console . debug ( "[getCMDVersion] Failed to get CMD version:" , error )
726+ return null
727+ }
728+ }
729+
730+ /**
731+ * Get terminal name and version information on Windows operating system
732+ * The retrieval logic is consistent with getShell, but returns terminal name and version information
733+ * Get accurate version information by executing commands
734+ *
735+ * @returns Terminal information object containing name, version and path
736+ */
737+ export async function getWindowsTerminalInfo ( ) : Promise < TerminalInfo | null > {
738+ if ( process . platform !== "win32" ) {
739+ return null
740+ }
741+
742+ const shellPath = getShell ( )
743+
744+ // Determine terminal name and version based on path
745+ if ( shellPath . toLowerCase ( ) . includes ( "pwsh.exe" ) ) {
746+ // PowerShell 7+
747+ const version = await getPowerShellVersion ( shellPath )
748+ return {
749+ name : "PowerShell" ,
750+ version : version || "Unknown" ,
751+ path : shellPath ,
752+ unsupportSyntax : terminalUnsupportedSyntax . powershell7 . unsupported ,
753+ features : terminalUnsupportedSyntax . powershell7 . features ,
754+ }
755+ } else if ( shellPath . toLowerCase ( ) . includes ( "powershell.exe" ) ) {
756+ // Windows PowerShell (5.1 and earlier versions)
757+ const version = await getPowerShellVersion ( shellPath )
758+ return {
759+ name : "Windows PowerShell" ,
760+ version : version || "5.1" ,
761+ path : shellPath ,
762+ unsupportSyntax : terminalUnsupportedSyntax . powershell5 . unsupported ,
763+ features : terminalUnsupportedSyntax . powershell5 . features ,
764+ }
765+ } else if ( shellPath . toLowerCase ( ) . includes ( "cmd.exe" ) ) {
766+ // Command Prompt
767+ const version = await getCMDVersion ( shellPath )
768+ return {
769+ name : "Command Prompt" ,
770+ version : version || "Built-in" ,
771+ path : shellPath ,
772+ unsupportSyntax : terminalUnsupportedSyntax . cmd . unsupported ,
773+ }
774+ } else if ( shellPath . toLowerCase ( ) . includes ( "bash.exe" ) ) {
775+ // Git Bash, MSYS2, MinGW, Cygwin, etc.
776+ if ( shellPath . toLowerCase ( ) . includes ( "git" ) ) {
777+ const { version } = await getGitBashVersion ( )
778+ return {
779+ name : "Git Bash" ,
780+ version : version || "Unknown" ,
781+ path : shellPath ,
782+ unsupportSyntax : terminalUnsupportedSyntax . gitBash . unsupported ,
783+ features : terminalUnsupportedSyntax . gitBash . features ,
784+ }
785+ } else if ( shellPath . toLowerCase ( ) . includes ( "msys64" ) ) {
786+ const { version } = await getGitBashVersion ( )
787+ return {
788+ name : "MSYS2" ,
789+ version : version || "64-bit" ,
790+ path : shellPath ,
791+ }
792+ } else if ( shellPath . toLowerCase ( ) . includes ( "msys32" ) ) {
793+ const { version } = await getGitBashVersion ( )
794+ return {
795+ name : "MSYS2" ,
796+ version : version || "32-bit" ,
797+ path : shellPath ,
798+ }
799+ } else if ( shellPath . toLowerCase ( ) . includes ( "mingw" ) ) {
800+ const { version } = await getGitBashVersion ( )
801+ return {
802+ name : "MinGW" ,
803+ version : version || "Unknown" ,
804+ path : shellPath ,
805+ }
806+ } else if ( shellPath . toLowerCase ( ) . includes ( "cygwin" ) ) {
807+ const { version } = await getGitBashVersion ( )
808+ return {
809+ name : "Cygwin" ,
810+ version : version || "Unknown" ,
811+ path : shellPath ,
812+ }
813+ } else {
814+ const { version } = await getGitBashVersion ( )
815+ return {
816+ name : "Bash" ,
817+ version : version || "Unknown" ,
818+ path : shellPath ,
819+ }
820+ }
821+ } else {
822+ // Unknown terminal
823+ return {
824+ name : "Unknown Terminal" ,
825+ version : "Unknown" ,
826+ path : shellPath ,
827+ }
828+ }
829+ }
0 commit comments