55
66import asyncio
77import os
8+ import shutil
89import socket
910import subprocess
1011import sys
@@ -239,19 +240,99 @@ def _aks_bastion_get_current_shell_cmd():
239240
240241 ppid = os .getppid ()
241242 parent = psutil .Process (ppid )
242- return parent .name ()
243+ parent_name = parent .name ()
244+ logger .debug (f"Immediate parent process: { parent_name } (PID: { ppid } )" )
245+
246+ # On Windows, Azure CLI is often invoked as az.cmd, which means the immediate parent
247+ # is cmd.exe but the actual user shell (PowerShell) is the grandparent process
248+ if sys .platform .startswith ("win" ):
249+ try :
250+ parent_exe = parent .exe ()
251+ logger .debug (f"Parent executable path: { parent_exe } " )
252+
253+ # If the immediate parent is cmd.exe, check if it's wrapping az.cmd for PowerShell
254+ if "cmd" in parent_name .lower ():
255+ try :
256+ # Get the grandparent process (parent of cmd.exe)
257+ grandparent = parent .parent ()
258+ if grandparent :
259+ grandparent_name = grandparent .name ()
260+ logger .debug (f"Detected grandparent process: { grandparent_name } (PID: { grandparent .pid } )" )
261+
262+ # If grandparent is PowerShell, that's the actual user shell
263+ if "pwsh" in grandparent_name .lower () or "powershell" in grandparent_name .lower ():
264+ logger .debug ("Grandparent is PowerShell - using PowerShell as target shell" )
265+ # Try to find pwsh first, fall back to powershell
266+ pwsh_path = shutil .which ("pwsh" )
267+ if pwsh_path :
268+ logger .debug (f"Found pwsh at: { pwsh_path } " )
269+ return "pwsh"
270+ powershell_path = shutil .which ("powershell" )
271+ if powershell_path :
272+ logger .debug (f"Found powershell at: { powershell_path } " )
273+ return "powershell"
274+ # If we can't find pwsh/powershell in PATH, use the detected grandparent
275+ logger .debug ("PowerShell not found in PATH, using detected grandparent executable" )
276+ return grandparent .exe () if grandparent .exe () else grandparent_name
277+ # If grandparent is not PowerShell, stick with cmd
278+ else :
279+ logger .debug ("Grandparent is not PowerShell - using cmd as target shell" )
280+ return "cmd"
281+ except (psutil .NoSuchProcess , psutil .AccessDenied ) as e :
282+ # If we can't access grandparent, assume cmd is the actual shell
283+ logger .debug (f"Cannot access grandparent process: { e } - using cmd as target shell" )
284+ return "cmd"
285+
286+ # For direct PowerShell processes (not wrapped by cmd), prefer pwsh over powershell.exe
287+ elif "pwsh" in parent_name .lower () or "powershell" in parent_name .lower ():
288+ logger .debug ("Direct PowerShell parent detected" )
289+ # Try to find pwsh first, fall back to powershell
290+ pwsh_path = shutil .which ("pwsh" )
291+ if pwsh_path :
292+ logger .debug (f"Found pwsh at: { pwsh_path } " )
293+ return "pwsh"
294+ powershell_path = shutil .which ("powershell" )
295+ if powershell_path :
296+ logger .debug (f"Found powershell at: { powershell_path } " )
297+ return "powershell"
298+ # If we can't find pwsh/powershell in PATH, use the detected parent
299+ logger .debug ("PowerShell not found in PATH, using detected parent executable" )
300+ return parent_exe if parent_exe else parent_name
301+ else :
302+ logger .debug (f"Other Windows shell detected: { parent_name } " )
303+ return parent_exe if parent_exe else parent_name
304+ except (psutil .NoSuchProcess , psutil .AccessDenied ) as e :
305+ logger .debug (f"Cannot access parent process details: { e } " )
306+ pass
307+
308+ logger .debug (f"Using parent process name as shell: { parent_name } " )
309+ return parent_name
243310
244311
245312def _aks_bastion_prepare_shell_cmd (kubeconfig_path ):
246313 """Prepare the shell command to launch a subshell with KUBECONFIG set."""
247314
248315 shell_cmd = _aks_bastion_get_current_shell_cmd ()
249316 updated_shell_cmd = shell_cmd
317+
318+ # Handle different shell types
250319 if shell_cmd .endswith ("bash" ) and os .path .exists (os .path .expanduser ("~/.bashrc" )):
251320 updated_shell_cmd = (
252321 f"""{ shell_cmd } -c '{ shell_cmd } --rcfile <(cat ~/.bashrc; """
253322 f"""echo "export KUBECONFIG={ kubeconfig_path } ")'"""
254323 )
324+ elif shell_cmd in ["pwsh" , "powershell" ] or "pwsh" in shell_cmd .lower () or "powershell" in shell_cmd .lower ():
325+ # PowerShell: Set environment variable and start new session
326+ # Use proper PowerShell syntax for setting environment variables
327+ escaped_path = kubeconfig_path .replace ("'" , "''" ) # Escape single quotes for PowerShell
328+ if shell_cmd == "pwsh" or "pwsh" in shell_cmd .lower ():
329+ updated_shell_cmd = f'pwsh -NoExit -Command "$env:KUBECONFIG=\' { escaped_path } \' "'
330+ else :
331+ updated_shell_cmd = f'powershell -NoExit -Command "$env:KUBECONFIG=\' { escaped_path } \' "'
332+ elif shell_cmd == "cmd" or "cmd" in shell_cmd .lower ():
333+ # CMD: Set environment variable and keep session open
334+ updated_shell_cmd = f'cmd /k "set KUBECONFIG={ kubeconfig_path } "'
335+
255336 return shell_cmd , updated_shell_cmd
256337
257338
@@ -260,6 +341,8 @@ def _aks_bastion_restore_shell(shell_cmd):
260341
261342 if shell_cmd .endswith ("bash" ):
262343 subprocess .run (["stty" , "sane" ], stdin = sys .stdin )
344+ # PowerShell and CMD on Windows typically don't need special restoration
345+ # as they handle terminal state management internally
263346
264347
265348async def _aks_bastion_launch_subshell (kubeconfig_path , port ):
0 commit comments