Is there a way to execute piped shell command in GnuProlog? - shell

Im trying to execute this piped command:
powershell.exe Get-ChildItem | Where-Object { $\_.Name -match "router" }
in GnuProlog:
:- initialization(main).
main :-
shell('powershell.exe Get-ChildItem \174\ Where-Object { $_.Name -match "router" }'), nl, halt.
It seems that shell predicate does not support piping, since the output is:
'Where-Object' is not recognized as an internal or external command, operable program or batch file.
warning: C:/prolog.pl:1: user directive failed
is there another way to do it?

I think you need to quote the whole argument in double quotes for Windows' system call handler to see it as one thing. And then edit the other quotes in it so they don't clash with either the Prolog atom single quotes or the PowerShell argument double quotes.
Try:
shell('powershell.exe -NoProfile -NonInteractive -Command "Get-ChildItem | Where-Object { $_.Name -match \'router\' }"'), nl, halt.
You might be able to skip the complexity of piping to Where-Object with:
Get-ChildItem -Filter *router*
PowerShell supports having the command code sent to it in Base64 encoded UTF16-LE, using the -EncodedCommand parameter; that exists to solve this kind of problem, and the effort of carefully quoting and escaping for shell handlers.

Related

How to start another PowerShell session in a separate window while keeping the environment?

While working in PowerShell I tend to quickly switch to admin mode by typing
Start-Process wt -Verb runas
When I do so, a new window appears (sadly, no sudo in Windows). In that new session however, the environment is totally fresh. Is it possible to keep variables, aliases, working dir and all other stuff of similar matter while jumping to a new window? If not, then well, it's a valid answer.
To give some example, I am looking for this behavior:
First window
C:\test> $x = 123
C:\test> Start-Process wt
New window
C:\test> $x
123
By (security-minded) design, elevated sessions (-Verb RunAs) do not inherit the caller's environment variables.
Also, wether or not you use -Verb RunAs, the state of a PowerShell session (aliases, functions, current location, ...) is never inherited when you launch another PowerShell process, such as with Start-Process.
You can work around the issue by explicitly and selectively redefining the state of interest via commands executed in the elevated session, based on values from the caller's state, but that is quite cumbersome and has limitations, as the following example shows:
# Define a few things to copy to the elevated session.
$x1 = 123
$x2 = '3" of snow' # !! See the caveat re regular variables below.
$env:foo = 1
$env:foo2 = 2
Set-Alias bar Get-Date
function baz { "hello, world" }
# Note: The following only copies the definitions above.
# You could try to copy ALL such definitions, by omitting a target name / pattern:
# Get-ChildItem env:
# Get-ChildItem function:
# Get-ChildItem alias:
# CAVEAT: This will NOT generally work with *regular variables*.
Start-Process -Verb RunAs powershell #"
-NoExit -Command Set-Location -LiteralPath \"$((Get-Location -PSProvider FileSystem).ProviderPath)\"
$(Get-Variable x? | ForEach-Object { "`${$($_.Name)} = $(if ($_.Value -is [string]) { "'{0}'" -f ($_.Value -replace "'", "''" -replace '"', '\"') } else { $_.Value }); " })
$(Get-ChildItem env:foo* | ForEach-Object { "Set-Item \`"env:$($_.Name)\`" \`"$($_.Value -replace '"', '\"\"')\`"; " })
$(Get-ChildItem function:bar | ForEach-Object { "`$function:$($_.Name) = \`"$($_.Definition -replace '"', '\"\"')\`"; " })
$(Get-ChildItem alias:baz | ForEach-Object { "`$alias:$($_.Name) = \`"$($_.Definition)\`"; " })
"#
Important:
I've omitted the call to Windows Terminal (wt.exe), as that would create another PowerShell session, which means that only the following definitions would be preserved for that session:
Environment variables.
The current location (working directory), IF its default shell is configured to use the parent process' working directory. Alternatively, and more predictably, pass the working dir. explicitly with the -d option: wt.exe -d \"$((Get-Location -PSProvider FileSystem).ProviderPath)\"
If that is enough, you can remove the commands that preserve aliases, functions, and regular variables, add -WindowStyle Hidden to Start-Process, remove -NoExit before -Command in the argument list, and add a wt.exe call at the bottom.
Preserving the other types of definitions requires working directly in the elevated powershell session, which will invariably use a regular (conhost.exe) console window, however.
In general, it's best to place definitions that should be available in both regular and elevated sessions in your $PROFILE file.
Complementarily, see this answer for convenience function Enter-AdminPSSession, which allows you to pass a script block to execute in the elevated session, to which you can pass values from the caller's state as arguments.
Note:
The above uses the Windows PowerShell CLI, powershell.exe. To use PowerShell (Core) 7+ instead, substitute pwsh.exe.
The above covers preserving the current file-system location, environment variables, aliases, and functions in a generic fashion.
Caveat: By contrast, preserving regular variables is limited to strings and numbers - in essence, instances of those data types whose stringified representation is recognized as such when interpreted as a source-code literal.
With nontrivial additional effort, supporting more data types is possible, by using Base64 encoding with the CLI's -EncodedCommand and -EncodedArguments parameters as shown in this answer, but the range of types that can be represented with type fidelity is fundamentally limited by PowerShell's XML-based serialization infrastructure - see this answer.
You can not keep variables, you will lose them immediately after the new window is created, the best you can do instead is to create a script containing all your activities then save it in the same working directory.
When you open a new window just call your script that will be able to give you the same information as in the other window.

How to escape poison character in this mixed cmd / powershell code?

I asked for a first explanation "here" and "here" but going to try a more complex situation I was unable (after two hours of trying) to understand how to solve. I read how the regular expression works but nothing, I went into the ball.
The modified code is this:
(Fsutil Dirty Query %SystemDrive%>Nul)||(powershell.exe -c "[Environment]::CommandLine; Start -Verb RunAs cmd /k, ("^""%~f0"^"" -replace '[;,()= &^]', '^$&')" & echo exit)
and the folder with the poison characters is this:
C:\Users\fposc\Desktop\Pie & tea % # ' $^
I have tried to escape the ^ in the regular expression with \^ but don't work. I have escaped also ( and ) with \( and \). But nothing work:
(Fsutil Dirty Query %SystemDrive%>Nul)||(powershell.exe -c "[Environment]::CommandLine; Start -Verb RunAs cmd /c, ("^""%~f0"^"" -replace '[;,\(\)= &\^]', '^$&')" & exit)
I added the round brackets because I wanted to put all possible characters to make the code as generic as possible.
I don't know if I was right to open another question. Maybe I should change the original question? Since other combinations are possible and not having understood the mechanism I could open many other similar questions. What do you advise me to do?
The problem is the presence of $ in your folder name, which causes the PowerShell command to interpret it as the start of a variable reference.
The workaround is to use an aux. environment variable to store the batch file's full path and let PowerShell perform its escaping based on this variable's value:
:: Unless already elevated, re-invoke this batch file with elevation,
:: via PowerShell.
set "__THISFILE=%~f0"
Fsutil Dirty Query %SystemDrive% >Nul || (powershell.exe -c "Start-Process -Verb RunAs cmd /k, ($env:__THISFILE -replace '[ &%%^]', '^$&')" & exit)
I have updated the answer to your original question to incorporate this approach, which now shows a - hopefully - robust approach to on-demand re-invocation of a batch file with elevation, including support for arguments.

Using PowerShell command and backtick newline inside Windows batch

I am trying to write a Windows batch file which will replace occurrences of angled brackets (><) with a newline in between.
I am new to PowerShell, but in searching though possible solutions, I have found the following works from PowerShell:
(get-content input.txt) -replace "><", ">`n<" | set-content output.txt
To use this within a windows batch, I need to wrap it inside
powershell -command "arguments"
So the final command is something like:
powershell -command "(gc input.txt) -replace '><', '>`n<' | sc output.txt"
However, this of course does not work because the single quotes around the replace text causes the grave quote escape character to be treated literally.
I have searched far and wide on the correct combination of escape characters to use to allow the PowerShell escape character to be recognised and have found a similar answer in here, but when I try this suggestion, I get a "< was unexpected at this time" error. I think what I need is more complicated because my search string also contains the angled brackets.
Look at the powershell.exe command line options. You can use a script block:
powershell -command {(gc input.txt) -replace "><", ">`n<" | sc output.txt}
Avoid using the escape character and double quotes?
powershell -command "(gc d:\t\input.txt) -replace '><', ('>'+[char]10+'<') | sc d:\t\output.txt"
I have solved the problem.
I also used delayed expansion, so the final command is:
powershell -Command "(gc !inputfile!) -replace (\"^>^<\", \"^>`n^<\") | sc !outputfile!"
So it actually uses three different types of escape characters! Combination of \ and ^ and `.
I wish I could say I worked it out logically, but in the end it was just a random attempt using different escapes on the ><.
But this is now a good reference on how to use PowerShell inside Windows batch without using single quotes which turn escape characters into literals.

Powershell command from cmd doesnt replace line breaks

I am trying to replace a string including a line break in a file. I am using the command line for this.
I am trying to use the same command in a CMD shell and in PowerShell, however I can only seem to get it to work in the latter.
Here is the command:
powershell -Command "(Get-Content client.properties -Raw).Replace('#test`r`n','test`r`n') | Set-Content client2.properties"
Why is this not working in a CMD shell, and how do I make it work?
The `r`n escape sequence won't work inside single-quotes.
Use the -replace operator instead and use regex escapes:
powershell -Command "(Get-Content client.properties -Raw)-replace('#test\r?\n','test'+$([Environment]::NewLine)) | Set-Content client2.properties"

Launch PowerShell from Command Prompt with custom prompt

I'm trying to open PowerShell with a customised prompt (for instance the UNIX shell prompt). I have tried:
powershell -noexit -command "& {function prompt {"$(pwd)$ "}}"
But it just starts powershell without the prompt I want. It does actually work in powershell itself. Could I get this to work or do I have to make a seperate file and do it through "-file"?
Not sure what the UNIX prompt defaults too but this should do what I think you want it to do.
powershell -noexit -command "function prompt {'{0}$ ' -f $pwd}"
If you use single quotes in the prompt function the $ doesn't get interpolated, and you don't have to worry about to many quotes.
SAVING THE PROMPT FUNCTION
Like any function, the Prompt function exists only in the current
session. To save the Prompt function for future sessions, add it to your
Windows PowerShell profiles. For more information about profiles,
see about_Profiles.
Here's how to create a new profile:
if (!(test-path $profile))
{new-item -type file -path $profile -force}
notepad $profile
Quoting on the command-line is tricky. Also, & runs a scriptblock in its own scope, so functions defined there don't "leak" out to the calling scope. The dot operator (also called dot-sourcing) is what you're looking for. This is what I got to work using backslashes to quote the strings.
powershell -noexit -command ". {function prompt {\"$(pwd)$ \"}}"
Add your custom prompt to your profile and it will load/run every time you start PowerShell.
Powershell customisation is always a bit tricky. Try adding a script with a method called prompt() like this:
function prompt() {
$myPrompt = "Ready>";
write-host -NoNewLine -ForegroundColor green $myPrompt
' '
}
Then call this in a profile, such as the one for all users:
%windir%\system32\Windows­PowerShell\v1.0\profile.ps1
Good luck!

Resources