MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4#nullable enable5 6using System;7using System.Collections;8using System.Collections.Generic;9using System.Management.Automation;10using System.Runtime.InteropServices;11 12using Dbg = System.Management.Automation.Diagnostics;13 14#if UNIX15 16namespace Microsoft.PowerShell.Commands17{18 /// <summary>19 /// Implements a cmdlet that allows use of execv API.20 /// </summary>21 [Cmdlet(VerbsCommon.Switch, "Process", HelpUri = "https://go.microsoft.com/fwlink/?linkid=2181448")]22 public sealed class SwitchProcessCommand : PSCmdlet23 {24 /// <summary>25 /// Get or set the command and arguments to replace the current pwsh process.26 /// </summary>27 [Parameter(Position = 0, Mandatory = false, ValueFromRemainingArguments = true)]28 public string[] WithCommand { get; set; } = Array.Empty<string>();29 30 /// <summary>31 /// Execute the command and arguments32 /// </summary>33 protected override void EndProcessing()34 {35 if (WithCommand.Length == 0)36 {37 return;38 }39 40 // execv requires command to be full path so resolve command to first match41 var command = this.SessionState.InvokeCommand.GetCommand(WithCommand[0], CommandTypes.Application);42 if (command is null)43 {44 ThrowTerminatingError(45 new ErrorRecord(46 new CommandNotFoundException(47 string.Format(48 System.Globalization.CultureInfo.InvariantCulture,49 CommandBaseStrings.NativeCommandNotFound,50 WithCommand[0]51 )52 ),53 "CommandNotFound",54 ErrorCategory.InvalidArgument,55 WithCommand[0]56 )57 );58 }59 60 var execArgs = new string?[WithCommand.Length + 1];61 62 // execv convention is the first arg is the program name63 execArgs[0] = command.Name;64 65 for (int i = 1; i < WithCommand.Length; i++)66 {67 execArgs[i] = WithCommand[i];68 }69 70 // need null terminator at end71 execArgs[execArgs.Length - 1] = null;72 73 var env = Environment.GetEnvironmentVariables();74 var envBlock = new string?[env.Count + 1];75 int j = 0;76 foreach (DictionaryEntry entry in env)77 {78 envBlock[j++] = entry.Key + "=" + entry.Value;79 }80 81 envBlock[envBlock.Length - 1] = null;82 83 // setup termios for a child process as .NET modifies termios dynamically for use with ReadKey()84 ConfigureTerminalForChildProcess(true);85 int exitCode = Exec(command.Source, execArgs, envBlock);86 if (exitCode < 0)87 {88 ConfigureTerminalForChildProcess(false);89 ThrowTerminatingError(90 new ErrorRecord(91 new Exception(92 string.Format(93 System.Globalization.CultureInfo.InvariantCulture,94 CommandBaseStrings.ExecFailed,95 Marshal.GetLastPInvokeError(),96 string.Join(' ', WithCommand)97 )98 ),99 "ExecutionFailed",100 ErrorCategory.InvalidOperation,101 WithCommand102 )103 );104 }105 }106 107 /// <summary>108 /// The `execv` POSIX syscall we use to exec /bin/sh.109 /// </summary>110 /// <param name="path">The path to the executable to exec.</param>111 /// <param name="args">112 /// The arguments to send through to the executable.113 /// Array must have its final element be null.114 /// </param>115 /// <param name="env">116 /// The environment variables to send through to the executable in the form of "key=value".117 /// Array must have its final element be null.118 /// <returns>119 /// An exit code if exec failed, but if successful the calling process will be overwritten.120 /// </returns>121 [DllImport("libc",122 EntryPoint = "execve",123 CallingConvention = CallingConvention.Cdecl,124 CharSet = CharSet.Ansi,125 SetLastError = true)]126 private static extern int Exec(string path, string?[] args, string?[] env);127 128 // leverage .NET runtime's native library which abstracts the need to handle different OS and architectures for termios api129 [DllImport("libSystem.Native", EntryPoint = "SystemNative_ConfigureTerminalForChildProcess")]130 private static extern void ConfigureTerminalForChildProcess([MarshalAs(UnmanagedType.Bool)] bool childUsesTerminal);131 }132}133 134#endif135 