Freigeben über


Run PowerShell as a shell within Emacs

I saw a couple people asking about running powershell as an inferior shell within emacs.  Here's what I do.

For the tragically unhip, some background: PowerShell is a nifty shell, it ships as an add-on to Windows.  Currently at v1.0, but the v2.0 is available as a CTP.  Emacs is the venerable supereditor that does 1001 things - one of those things is the ability to run a shell within a buffer.  Emacs runs on Unix and Windows, and the shell stuff is configurable across a wide variety of shells and operating systems.  As I was saying, some people have expressed interest in running PowerShell as an inferior shell within Emacs.

We're not talking about a powershell script editing mode. That problem has been solved. Instead, we're talking about running a powershell interactively within emacs.

There are a couple challenges with doing this.  First, emacs is complex, and it is hard for people to figure out to do what they want.  Even the doc is impenetrable.  Basically you just have to KNOW how to do something, or someone else shows you.  Getting back to our situation, to explicitly specify the shell to run, in emacs you set a variable called explicit-shell-file-name. Great, so we set this to c:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe. There's an example for starting shells that says "to set the arguments to the shell, set the explicit-sh-args variable. People were setting this variable and trying to run PowerShell but getting an error. That's because the variable name for arguments is different for each shell.  For bash, sh is the image and the variable is explicit-sh-args.  For powershell, the variable to set is explicit-powershell.exe-args . Of course!  This isn't explicitly documented anywhere (that I could see), but if you read the source code for shell.el you will see it. And, to further complicate things, if you don't explicitly set anything in that args variable, shell.el implicitly includes -i as an argument, which makes powershell barf.  -i is a valid powershell argument, but it needs another param.  Hence the barf.  Anyway, to avoid all this, set explicit-powershell.exe-args .   Example:

   (setq explicit-shell-file-name "c:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe")
  (setq explicit-powershell.exe-args '("-Command" "-" )) ; interactive, but no command prompt 

After setting these variables, to launch powershell from within emacs, just use M-x shell. This gets you to the second problem: powershell goes into "noninteractive mode".  It emits no prompts, although it reads from the stdin and puts output on stdout.  So you can see the input and output, but no prompts at all.  Needless to say, it's a bit unfriendly to do this.

To fix this up we have to ask powershell to give us prompts.  A simple thing to do would be to launch powershell in interactive mode, despite the fact that stdin and stdout have been redirected.  There's an option on powershell.exe to launch it in "noninteractive mode" which means no prompts, and which is already what we have.  We want the converse.  I couldn't figure a way to do the "force interactive mode", so I used some emacs lisp to sort of fake it.

Within shell mode, there are "hooks" that emacs will call, on certain events, and overrides that you can set. (Sort of like the channel stack in WCF.)  One possible override is the "send command to shell" method. You can write some custom code that runs, every time the user types in a command to the shell, in order to send the command to the shell. You can filter it, modify it, log it, suppress it, whatever.  I wrote a simple wrapper in elisp that sends the original command, and then ALSO sends the "prompt" command.  This causes powershell to emit the prompt, after the first, explicit command is run.  Cool.  That's what we want.

Then there are some rough edges. For example, powershell assumes an 80-character width for a terminal in non-interactive mode.  You can set this by tickling the RawUI object within powershell itself.  This is made possible with another hook.  Also, I need to resize the powershell terminal whenever the emacs window changes.  So I need to hook the window-size-change event within emacs.

One last thing is that the powershell prompt comes out with a trailing newline.  So I need to trim that off as well, so that the next command you type in the powershell inferior shell, is just to the right of the powershell prompt.

I wrapped all this up into a new shell mode. The result is the brief bit of elisp attached here. It's pretty bare-bones, but it's a start.  And it lets you do what you want - run powershell as an inferior shell within emacs.

 ;; powershell.el, version 0.1
;;
;; Author: Dino Chiesa
;; Thu, 10 Apr 2008  11:10
;;
;; Run Windows PowerShell v1.0 as an inferior shell within emacs. Tested with emacs v22.2.
;;
;; TODO:
;;  test what happens when you expand the window size beyond the maxWindowWidth for the RawUI
;;  make everything configurable (Powershell exe, initial args, powershell prompt regexp)
;;  implement powershell launch hooks
;;  prevent backspace from deleting the powershell prompt? (do other shells do this?)
;;

(require 'shell)


(defun powershell-gen-window-width-string ()
  (concat  "$a = (Get-Host).UI.RawUI\n" 
            "$b = $a.WindowSize\n"
            "$b.Width = " (number-to-string  (window-width)) "\n"
            "$a.BufferSize = $b\n"
            "$a.WindowSize = $b")
  )
  

(defvar powershell-prompt-pattern  "PS [^#$%>]+>" 
  "Regexp for powershell prompt.  This isn't really used, because I couldn't figure out how to get it to work."
  )

(defgroup powershell nil
  "Running shell from within Emacs buffers."
  :group 'processes
  )


(defcustom powershell-need-rawui-resize t
  "set when powershell needs to be resized"
  :group 'powershell
)

;;;###autoload
(defun powershell (&optional buffer)
  "Run an inferior powershell, by invoking the shell function. See the help for shell for more details.
\(Type \\[describe-mode] in the shell buffer for a list of commands.)"
  (interactive
   (list
    (and current-prefix-arg
         (read-buffer "Shell buffer: "
                      (generate-new-buffer-name "*PowerShell*")))))
  ; get a name for the buffer
  (setq buffer (get-buffer-create (or buffer "*PowerShell*")))

  (let (
        (tmp-shellfile explicit-shell-file-name)
        )
                                        ; set arguments for the powershell exe.
                                        ; This needs to be tunable.
    (setq explicit-shell-file-name "c:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe")  
    (setq explicit-powershell.exe-args '("-Command" "-" )) ; interactive, but no command prompt
  
                                        ; launch the shell
    (shell buffer)

    ; restore the original shell
    (if explicit-shell-file-name
        (setq explicit-shell-file-name tmp-shellfile)
      )
    )
  
  (let (
        (proc (get-buffer-process buffer))
        )
    
    ; This sets up the powershell RawUI screen width. By default,
    ; the powershell v1.0 assumes terminal width of 80 chars.
    ;This means input gets wrapped at the 80th column.  We reset the
    ; width of the PS terminal to the window width. 
    (add-hook 'window-size-change-functions 'powershell-window-size-changed)

    (powershell-window-size-changed)
    
    ; ask for initial prompt
    (comint-simple-send proc "prompt")
    )

  ; hook the kill-buffer action so we can kill the inferior process?
  (add-hook 'kill-buffer-hook 'powershell-delete-process)

  ; wrap the comint-input-sender with a PS version
  ; must do this after launching the shell! 
  (make-local-variable 'comint-input-sender)
  (setq comint-input-sender 'powershell-simple-send)

  ; set a preoutput filter for powershell.  This will trim newlines after the prompt.
  (add-hook 'comint-preoutput-filter-functions 'powershell-preoutput-filter-for-prompt)

  ;(run-hooks 'powershell-launch-hook)

  ; return the buffer created
  buffer
)


(defun powershell-window-size-changed (&optional frame)
  ; do not actually resize here. instead just set a flag.
  (setq powershell-need-rawui-resize t)
)



(defun powershell-delete-process (&optional proc)
  (or proc
      (setq proc (get-buffer-process (current-buffer))))
  (and (processp proc)
       (delete-process proc))
  )



;; This function trims the newline from the prompt that we
;; get back from powershell.  It is set into the preoutput
;; filters, so the newline is trimmed before being put into
;; the output buffer.
(defun powershell-preoutput-filter-for-prompt (string)
   (if
       ; not sure why, but I have not succeeded in using a variable here???  
       ;(string-match  powershell-prompt-pattern  string)

       (string-match  "PS [^#$%>]+>" string)
       (substring string 0 -1)
     
     string

     )
   )



(defun powershell-simple-send (proc string)
  "Override of the comint-simple-send function, specific for powershell.
This just sends STRING, plus the prompt command. Normally powershell is in
noninteractive model when run as an inferior shell with stdin/stdout
redirected, which is the case when running as a shell within emacs.
This function insures we get and display the prompt. "
  ; resize if necessary. We do this by sending a resize string to the shell,
  ; before sending the actual command to the shell. 
  (if powershell-need-rawui-resize
      (and
       (comint-simple-send proc (powershell-gen-window-width-string))
       (setq powershell-need-rawui-resize nil)
       )
    )
  (comint-simple-send proc string)
  (comint-simple-send proc "prompt")
)

To use it, you need to add in this text in your .emacs file:

 (autoload 'powershell "powershell" "Run powershell as a shell within emacs." t) 

Start powershell within emacs with M-x powershell. You can continue to use the regular Windows shell within emacs via M-x shell.

Cheers!

powershell.el

Comments

  • Anonymous
    April 10, 2008
    Here's the proof that you can run powershell as a shell in Emacs . Grab the download from yesterday to

  • Anonymous
    April 14, 2008
    that is awesome! it just works...

  • Anonymous
    April 21, 2008
    The beanshell is something that, I think, was specially engineered to support the JDE, and particularly

  • Anonymous
    May 29, 2008
    This is great!  I just have one problem.  Powershell's TAB completion is broken.  I've looked into the emacs tab completion and I can't figure out how to get it to work as Powershell's does.  Perhaps you've already figured this out? I tried remapping the TAB key to comint-self-insert, but that didn't work for whatever reason.  Thanks for this great article.

  • Anonymous
    May 29, 2008
    I hadn't confronted the TAB thing.  It shouldn't be too hard to finagle that.

  • Anonymous
    June 15, 2008
    This is a great stuff. However, I don't know how to break an incomplete cmdlet. In the Shell mode, I can do Ctrl-C Ctrl-C to break a subjob. In PowerShell mode, it does not work.

  • Anonymous
    June 27, 2008
    If you are writing Lisp, I suggest you follow consistent coding style -  http://www.lisp.org/table/style.htm

  • Anonymous
    July 02, 2008
    Hey using your lisp script also I am unable to get the prompt

  • Anonymous
    May 19, 2009
    Hmm, I actually get shell mode completion, but only for items on my path, not on powershell built-ins and aliases.  I need to research this.

  • Anonymous
    July 03, 2009
    hi, is your script open source or something? may i have permission to put it on my website? i have a emacs tutorial here http://xahlee.org/emacs/emacs.html but am writing a simple tutorial for powershell, not yet public. Am thinking to include your script there, possibly mod a bit, or perhaps send you back any change if you like. Also, am talking to emacsw32 guy Lennart and we hope to include your in that emacs distro. thanks.

  • Anonymous
    July 23, 2009
    Yes, you can take that script and do what you like with it. It's just a sample.  no warrantees.

  • Anonymous
    July 23, 2009
    Would love to see it in the emacs distro, whatever I can do to help, I will.

  • Anonymous
    January 25, 2010
    The OS is not always on the C: drive. The following patch handles that issue. --- powershell.el.orig 2010-01-10 10:26:13.328125000 -0500 +++ powershell.el 2010-01-25 19:37:36.375000000 -0500 @@ -56,7 +56,9 @@         )                                         ; set arguments for the powershell exe.                                         ; This needs to be tunable.

  •    (setq explicit-shell-file-name "c:\windows\system32\WindowsPowerShell\v1.0\powershell.exe")  
  •    (setq explicit-shell-file-name
  •          (concat (getenv "WINDIR")
  •                  "\system32\WindowsPowerShell\v1.0\powershell.exe"))     (setq explicit-powershell.exe-args '("-Command" "-" )) ; interactive, but no command prompt                                         ; launch the shell
  • Anonymous
    January 28, 2010
    good catch, thanks for the pointer.

  • Anonymous
    April 12, 2010
    The comment has been removed

  • Anonymous
    February 12, 2015
    I was trying to do the same thing but in C++ by redirecting stdin and stdout from PowerShell and couldn't figure it out. This helped tremendously ! Thank you cheeso!!