CDSoft / pp

PP - Generic preprocessor (with pandoc in mind) - macros, literate programming, diagrams, scripts...
http://cdelord.fr/pp
GNU General Public License v3.0
252 stars 21 forks source link

Add PowerShell Support #17

Closed tajmone closed 7 years ago

tajmone commented 7 years ago

I think it would be a worth adding support for PowerShell via a built in macro (!ps, !powershell or !posh) as an alternative to !cmd.

PowerShell is way more powerful and flexible compared to CMD, and ships with many Unix like commands (ls, etc.), and there are lots of third party scripts availabe to emulate Linux commands (touch, etc.). This could simplify creating cross-platform macros because it would allow to use some common commands syntax (ie: and ls command would also work with !ps, unlike !cmd requiring DIR).

PowerShell scripts are less clancky and more user friendly than batch files, and — most important — the PowerShell environment doesn't suffer from the lack of full Unicode/UTF8 compliance as the CMD does (many commands break up unicode in piping operations, as not all CMD commands are fully unicode compliant yet, and there is the usual page encoding nightmare).

CDSoft commented 7 years ago

The problem with Microsoft specific languages is that they are not standard, not portable, not cross-platform, not sustainable. I would prefer to promote standard (POSIX) and widespread (Python) tools (ok, Haskell is not really widespread but I love it ;-).

Windows users could even use bash with BusyBox (no need to install a full Cygwin). BusyBox is a pretty complete bash with a set of Unix commands and it's very light.

To me, bash and python are enough. bash for simple scripts and Python for more complex algorithms. If more languages are needed they can easily be embedded in bash scripts.

tajmone commented 7 years ago

I did look into BusyBox after you mentioned it here, and it's indeed a very nice solution (and small). I didn't have a chance to set it up yet, I wanted to check if there might be clashes with Git Bash (but also, I have a VMWare box with a full Linux at hand).

Sure, any installed language can be accessed through the CMD interface — I've used some pp-macros to import AsciiDoctory tables (column and rows spanning, etc.), and since it's a RubyGem it becomes just a command available from the CLI. The same with Node.js (and even Python for that matter).

I thought that PowerShell would be more sustainable than the CMD.

As for Haskell: I'd say it's a mandatory (and obvious) choice since pandoc also support native Haskell filters; so it's definitely good to have it.

I'll try to resume on my tutorials pages some of these considerations, and provide links.

CDSoft commented 7 years ago

I can add a powershell macro. Can you give me the command line to execute a powershell script? But I won't be able to test it...

tajmone commented 7 years ago

There are different ways to run a scripts in PowerShell, so ideally any PS macro should offer an optional parameter for user defined command line options/parameters.

As for executing a script coming from STDIN, these are two basic ways:

-Command

Executes the specified commands (and any parameters) as though they were typed at the Windows PowerShell command prompt, and then exits, unless the NoExit parameter is specified.

The value of Command can be "-", a string. or a script block. If the value of Command is "-", the command text is read from standard input.

Script blocks must be enclosed in braces ({}). You can specify a script block only when running PowerShell.exe in Windows PowerShell. The results of the script are returned to the parent shell as deserialized XML objects, not live objects.

If the value of Command is a string, Command must be the last parameter in the command , because any characters typed after the command are interpreted as the command arguments.

To write a string that runs a Windows PowerShell command, use the format:

"& {<command>}"

where the quotation marks indicate a string and the invoke operator (&) causes the command to be executed.

MSDN: PowerShell.exe Command-Line Help

Dot sourcing might have to be kept in account if script variables need to be preserved in the environment:

PowerShell -Command ". some command"

Dot Sourcing When you dot source a script, all variables and functions defined in the script will persist even when the script ends. SS64: Run a PowerShell script

You could declare this feature as experimental, and I'll test and experiment with it.

Having just a PowerShell macro that accepts a parameter and passes it to the powershell executable would be more than enough — an let the user handle all the nitty gritty.

Some reference links:

CDSoft commented 7 years ago

I guess -File is the right option to execute a script from a file (pp creates a temporary file before executing a script, whatever the language).

CDSoft commented 7 years ago

I have pushed a version with a \powershell macro. You can try this for instance:

\powershell
````````````````````````````````
echo "this is a powershell script"
````````````````````````````````

If it works I will add a tag.

tajmone commented 7 years ago

I've donwloaded v1.5-5-gd587a7e and tested the above macro from both CMD and PowersShell (x64) and it worked in both cases (emitted: this is a powershell script in the document).

tajmone commented 7 years ago

Sometimes when invoking it via CMD and a batch file, it hangs forever.

If I redirect pp's output to a file, the file is created with zero bytes and the process hangs. Further attempts to run the batch (from either PS or CMD) will raise an error about the destination file being opened by another app. (I've looked into the running processes tree but couldn't work out which process is hanging, I had to disconnect the Win session and relog).

But it doesn't occur always: usually only the first time the batch is invoked (ie: if I rename the redirection filename in the batch script and give it another run it usually succeeds). It seems to be on of those "Windowy things" hard to explain, and that the problem might be in the passage from CMD to PowerShell and back to CMD (which is handling redirection), so it might be just an issue of timing and/or cache (since it works on second tries).

Still, invocation from a batch file using redirection is a likely scenario. And in a macros workflow mixing CMD and PowerShell macros wouldn't be so rare.

I'll try to think of some edge testing, so that I might figure out the exact nature of the problem (this thing of the "file in use by another process" is a rather blocking situation, and it slows down testing because of the necessity to logout, log back-in and reopen all files, folder and apps).

But the macro itself definitely works.

tajmone commented 7 years ago

Stackoverflow mentions a problem that might be related to this:

Try adding the /WAIT parameter. It will keep the .bat waiting until the PowerShell script completes.

START /WAIT powershell "& "C:\data\etc\run_import_script.ps1"

... this would apply to an actual batch command, but usually the WinAPI offers similar WAIT options for file operations too (I remember stumbling on similar problems while programming some apps that interacted with external tools).

The Stackoverflow thread also mentions other considerations and "tricks". User Gordon notes:

From my experience, PowerShell.exe can easily run scripts from within a batch file or shell script in a predictable way using the -File switch. One does not need to use the Start command.

The important thing to do is to append < nul to the command line from within a batch file. My research has shown that PowerShell runs the commands in the script indicated through the -File switch and then waits for additional PowerShell commands from the standard input [...] By redirecting the standard input to nul, once PowerShell finishes executing the script and "reads end-of-file" from the standard input, PowerShell exits.

When invoking PowerShell from Cygwin, use < /dev/null For example, I've run PowerShell scripts from Cygwin using shell variables, like this:

PowerShell.exe -ExecutionPolicy RemoteSigned -File $_powershellscriptpath $_firstscriptparameter < > /dev/null

maybe this explains the hanging (but it wouldn't explain why sometimes the same script worked with a renamed file, except that CMD does behave oddly sometimes).