Pipe into executable through .lnk file - powershell

I have an executable at C:\Very\Long\Path\StreamToClipboard.exe
The file C:\InPath\StreamToClipboard.lnk points to that executable.
The directory C:\InPath is in my PATH variable, and .lnk is in my PATHEXT variable.
In regular cmd, I can execute any of these commands:
echo Hello | C:\Very\Long\Path\StreamToClipboard.exe
echo Hello | C:\InPath\StreamToClipboard.lnk
echo Hello | StreamToClipboard.lnk
echo Hello | StreamToClipboard
and the the executable is started, the text "Hello" is correctly piped into that process.
In PowerShell, I can execute echo Hello | C:\Very\Long\Path\StreamToClipboard.exe and it works too. But all of the other commands don't work:
Fehler beim Ausführen des Programms "StreamToClipboard.lnk": Die angegebene ausführbare Datei ist keine gültige
Anwendung für diese Betriebssystemplattform.In Zeile:1 Zeichen:12
+ echo foo | C:\InPath\StreamToClipboard.lnk
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.
In Zeile:1 Zeichen:1
+ echo foo | C:\InPath\StreamToClipboard.lnk
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (:) [], ApplicationFailedException
+ FullyQualifiedErrorId : NativeCommandFailed
(and respective paths)
Which roughly translates to Error while executing "StreamToClipboard.lnk": The specified executable file is not a valid application for this operating system plattform.
Note that echo Hello | "C:\Very\Long\Path\StreamToClipboard.exe" also does not work, with a different error message:
In Zeile:1 Zeichen:14
+ ... cho Hello | "C:\Very\Long\Path\StreamToClipboard.exe"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ausdrücke sind nur als erstes Element einer Pipeline zulässig.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ExpressionsMustBeFirstInPipeline
Roughly translates to Expressions are only valid as the first element in a pipeline.
If I, instead of creating the .lnk file, copy the entire executable to C:\InPath\StreamToClipboard.exe, then these commands:
echo Hello | C:\InPath\StreamToClipboard.exe
echo Hello | StreamToClipboard.exe
echo Hello | StreamToClipboard
work fine.
How can I get PowerShell to accept echo Hello | StreamToClipboard (where it's an .lnk file), or at least echo Hello | StreamToClipboard.lnk?

Unlike cmd.exe, PowerShell does not support invoking shortcut files (.lnk) like console applications; instead:
A new console window opens, asynchronously.
The process in the new console window does NOT receive stdin input (via the pipeline):
If a command is used - such as echo hello, using the built-in echo alias for PowerShell's (rarely needed) Write-Output cmdlet - the following error occurs, as of Windows PowerShell v5.1 / PowerShell (Core) 7.2.4:
Cannot run a document in the middle of a pipeline
If an expression - such as 'hello' - is used, no error occurs, but the input is effectively ignored.
In other words: PowerShell considers .lnk files documents, not executables, and defers to Windows (GUI) shell for opening them;[1] in effect, invoking a .lnk file is like passing it to Invoke-Item or Start-Process; adding -Wait to the latter makes the invocation synchronous, but still runs in a separate window and doesn't support stdin input; attempting to use -NoNewWindow and/or -RedirectStandardInput (to provide stdin input via a file) results in an error, similar to the one you saw:[2]
This command cannot be run due to the error: %1 is not a valid Win32 application.
Workarounds:
Call the target .exe file directly in your pipeline, which - if your path is quoted and/or contains variable references or expressions - requires use of &, the call operator:
# & is required, because the executable path is quoted.
# Note: 'Hello' is the PowerShell-idiomatic equivalent of
# echo Hello
'Hello' | & "C:\Very\Long\Path\StreamToClipboard.exe"
Note: For simplicity, you may choose to always use & when invoking external programs, but it is only required in the cases mentioned above, discussed in more detail in this answer.
Invoke the shortcut (.lnk) file via cmd /c:
'Hello' | cmd /c C:\InPath\StreamToClipboard.lnk
Note that, in both workarounds, character encoding issues may arise, given that data is being sent to an external program:
The $OutputEncoding preference variable controls what encoding is used to send data to an external program.
The encoding stored in [Console]::OutputEncoding determines how PowerShell decodes data received from external programs.
See this answer for more information.
[1] Note that (temporarily) appending ";.LNK" to the value of $env:PATHEXT, the environment variable that lists all filename extensions that belong to executables, does not help.
[2] The reason is that these parameters cause Start-Process to switch from the ShellExecute WinAPI function to CreateProcess, and the latter only works with actual executables.

Related

Uninstall program using get-process and uninstallstring with call operator (&)

I'm trying create a uninstall script to uninstall any program tha I want.
I was using this script:
get-package -Name "XXXXX" | Uninstall-Package
But I saw that when the "provideName" attribut is "Program" it not working, so I use this.
$packageArray = get-package "*XXXX*"
foreach ($package in $packageArray){
$a = $package.ProviderName
if ($a -eq "Programs"){
& {($package.Meta.Attributes["UninstallString"] -replace '"') /S}
}
if ($a -ne "Programs"){
get-package -Name "*XXXXX*" | Uninstall-Package
}
}
This part bellow was working fine when I have not ""&quot."", like this.
& {($package.Meta.Attributes["UninstallString"] -replace '"') /S}
Print:
But now I'm getting erro when a uninstall string has "&quot." value, like this.
UninstallString=""&quot";C:\Users\rsantanna\AppData\Local\GoToMeeting\19950\G2MUninstall.exe" /uninstall"
Print.
When it occur I get this error.
& : The term 'C:\Users\rsantanna\AppData\Local\GoToMeeting\19950\G2MUninstall.exe /uninstall' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the
path is correct and try again.
At line:6 char:7
& ($UninstallString)
~~~~~~~~~~~~~~~~~~
CategoryInfo : ObjectNotFound: (C:\Users\rsanta....exe /uninstall:String) [], CommandNotFoundException
FullyQualifiedErrorId : CommandNotFoundException
Anyone can help me ?
You cannot directly execute a string expression such as ($package.Meta.Attributes["UninstallString"] -replace '"')
While you can do so via &, the call operator, it only works if the string expression evaluates to a command name or executable file path only.
Notably, you can not use & to execute an entire command line, which is what $package.Meta.Attributes["UninstallString"] typically contains.
Note that your use of & is with a script block ({ ... }), which allows you to execute arbitrary statements contained in the block; however, perhaps needless to say, these statements are subject to the usual syntax rules, and it is the one and only statement in your block that causes a syntax error (that is actually different from the error you report).
As explained in detail in this answer, the command lines stored in UninstallString registry values are designed for invocation from cmd.exe or from no-shell environments.
Therefore, the simplest solution is to call via cmd.exe /c:
cmd.exe /c "$($package.Meta.Attributes["UninstallString"]) /S"
If your uninstall command line, as returned from $package.Meta.Attributes["UninstallString"], really contains " in lieu of verbatim " characters (double quotes) - which would be unusual - you'll need to replace the former with the latter first:
cmd.exe /c "$($package.Meta.Attributes["UninstallString"] -replace '"', '"') /S"
It's unclear what command produced the " output in your screenshot, but given that it shows the text of an XML document, the use of " instead of " is simply a necessity for encoding literal " chars.

Get the exact command error output displayed in console into a Powershell variable

I'm writing some scripts to backup registry and I encountered 2 small issues. The 1st one is that I can't intercept the error output of the reg export command.
Example:
reg export HKEY_CURRENT_USER\Software\OpenVPN-GUI test.reg
Writes this to console because the key is not available:
ERROR: The system was unable to find the specified registry key or value.
I would love to get this exact string.
Assigning the command to a variable results in an empty string, even with Out-String:
$regerror = reg export HKEY_CURRENT_USER\Software\OpenVPN-GUI test.reg | Out-String
The closest solution I've found is this kind of redirection:
$regerror = & reg export HKEY_CURRENT_USER\Software\OpenVPN-GUI test.reg 2>&1
But that displays:
reg.exe : ERROR: The system was unable to find the specified registry key or value.
At line:1 char:9
+ $test = & reg export HKEY_CURRENT_USER\Software\OpenVPN-GUI test.reg ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (ERROR: The syst...y key or value.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
I'm wondering if there's a simple way to get the mentioned exact output without attempting to process the text above.
Simply convert the stderr lines to strings:
$regerror =
reg export HKEY_CURRENT_USER\Software\OpenVPN-GUI test.reg 2>&1 |
ForEach-Object ToString
While stderr are are strings to begin with, PowerShell wraps them in [System.Management.Automation.ErrorRecord] instances when you use a 2> redirection, which is what causes them to render like PowerShell errors when printed to the display.
(In PowerShell (Core) 7+, they no longer render as such, but they're still wrapped in [System.Management.Automation.ErrorRecord] instances).

Newbie powershell argument issues

I made powershell script that [1] accepts 2 arguments (aka parameters), [2] changes a file's modified date & time, and [3] writes something to host. The following command line works just fine in the powershell console, but triggers an error message when I run the same command line in a Windows cmd prompt (DOS) Window:
E:\Apps\UtilitiesByMarc\Change_DateTime_for_test1.bat_and_Hello_world_with_2_named_args_aaa.ps1 -dateTimeVarArg "01/11/2005 06:01:36" -file_dateTimeMod_fullname "E:\Apps\Delete01\test1.bat"
The following is the coding for the powershell script to which I gave the long name, 'Change_DateTime_for_test1.bat_and_Hello_world_with_2_named_args_aaa.ps1':
param ( [string]$dateTimeVarArg, [string]$file_dateTimeMod_fullname)
Get-ChildItem $file_dateTimeMod_fullname | % {$_.LastWriteTime = $dateTimeVarArg}
#Get-ChildItem "E:\Apps\Delete01\test1.bat" | % {$_.LastWriteTime = $dateTimeVarArg}
$strString = "Hello World"
write-host $strString
function ftest{
$test = "Test"
write-host $test
}
ftest
When I run the command line shown above in a Windows DOS command prompt setting, I get the following error message:
Exception setting "LastWriteTime": "Cannot convert null to type "System.DateTime"."
At E:\Apps\UtilitiesByMarc\Change_DateTime_for_test1.bat_and_Hello_world_with_1_named_arg_aaa.ps1:6 char:50
+ ... "E:\Apps\Delete01\test1.bat" | % {$_.LastWriteTime = $dateTimeVarArg}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting
I would like to know [1] how to alter the command line shown above (which works fine in the powershell console) so that it works in a Windows DOS command prompt setting, and [2] where I can learn more about why my command line triggers errors, and how to avoid them.
According to the output from the command "Get-Host | Select-Object Version", I am running version. 5.1.19041.1682.
Any tips would be much appreciated.
By default, you can not directly execute PowerShell scripts (.ps1 files) from cmd.exe, the Windows legacy shell, or from outside PowerShell altogether.
Attempting to do so opens the script file for editing instead, as does double-clicking .ps1 files from File Explorer / the desktop.
Executing .ps1 scripts from outside PowerShell itself requires use of the PowerShell CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell (Core) 7+), which in your case translates to a call such as (additional CLI parameters may be called for):
powershell.exe -File E:\Apps\UtilitiesByMarc\Change_DateTime_for_test1.bat_and_Hello_world_with_2_named_args_aaa.ps1 -dateTimeVarArg "01/11/2005 06:01:36" -file_dateTimeMod_fullname "E:\Apps\Delete01\test1.bat"
As for what you tried:
The fact that you were able to execute your .ps1 from cmd.exe suggests that you changed the file-type definition for such files to execute via powershell.exe instead.
The fact that the arguments you tried to pass to the script were ignored - as implied by the error message you got - suggests that you used File Explorer's Open with shortcut-menu command to choose to open all .ps1 files with powershell.exe; said method does not support argument-passing.
There is a way to change the file-type definition to support argument-passing too, and it is detailed in this answer (section "Programmatic method").
Generally, however, I suggest not applying this customization, especially in batch files that must also be run by other users / on other machines, which cannot be expected to have the same customization in place.

Powershell can not call ImageMagick commands - IM Montage

I wrote a script in PowerShell and I am having various success calling Image Magick's montage.exe on different computers. The computer on which I wrote the script has no problem executing the 'montage' command, however on another computer with IM Installed the script errors out:
montage : The term 'montage' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At \\Server\Contact_Sheet_Local.ps1:51 char:9
+ montage -verbose -label %t -pointsize 20 -background '#FFFFFF ...
+ ~~~~~~~
+ CategoryInfo : ObjectNotFound: (montage:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
I have tried using montage.exe and even tried the entire path C:\Program Files\ImageMagick-7.0.3-Q16\montage.exe. Additionally I tried setting the directory first:
Set-Location -Path 'C:\Program Files\ImageMagick-7.0.3-Q16'
montage...
Each time on a particular computer this fails. I have tried with IM versions 7.0.3-Q16 and 6.9.1-6-Q8 both x64 as both computers are x64
In the past, I have created scripts in .bat that use ImageMagick and I had to define the full path to the .exe as I mentioned above. But this doesn't seem to help in PowerShell.
Does anyone have any advice or experience with this problem?
If your path has spaces, it will fail if you're just trying to execute based on that. You'll want to utilize the dot operator
$Exe = 'C:\Program Files\ImageMagick-6.9.1-6-Q8\montage.exe'
If (-not (Test-Path -Path $Exe))
{
$Exe = 'C:\Program Files\ImageMagick-7.0.3-Q16\montage.exe'
}
. $Exe -arg1 -etc
PowerShell does not execute programs in the current directory by default. If you want to run an executable that's sitting in the current directory, prefix the executable's name with .\ or ./. Example:
Set-Location "C:\Program Files\ImageMagick-7.0.3-Q16"
.\montage.exe ...
If you have an executable's name in a string or string variable and you want to execute it, you can do so using the & (call or invocation) operator:
& "C:\Program Files\ImageMagick-7.0.3-Q16\montage.exe" ...
If you specify a path and filename that doesn't contain spaces, the & operator isn't required; example:
C:\ImageMagick\montage.exe ...
You could also write it this way:
& C:\ImageMagick\montage.exe ...
If you have an executable's filename in a string variable and you want to execute it, use &; example:
$execName = "C:\Program Files\ImageMagick-7.0.3-Q16\montage.exe"
& $execName ...

Use Tee-Object with redirecting

I want to run this command and redirect all the output to Windows as well as a log file.
powershell "C:\backup\backup.bat *>&1 | tee log.txt"
so when I run the command I can see the output and also save it in a file, but I am getting this error:
Ampersand not allowed. The & operator is reserved for future use; use "&" to pa
ss ampersand as a string.
At line:1 char:25
+ C:\backup\backup.bat *>& <<<< 1 | tee log.txt
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordEx
ception
+ FullyQualifiedErrorId : AmpersandNotAllowed
To get the output of .bat file execution to the console as well as to file, use:
powershell "& 'C:\backup\backup.bat' *>&1 | Tee-Object -FilePath 'log.txt'"
There's a good post, PowerShell and external commands done right, which explains how to start external command. After that simply apply redirection as in the article you linked.
Redirection of streams other than Success and Error (AKA STDOUT and STDERR) isn't supported prior to PowerShell v3, as #CB. mentioned in comments. In PowerShell v2 you can only merge the Error stream:
powershell "C:\backup\backup.bat 2>&1 | tee log.txt"