PowerShell, only "> $out" catch full error of failed 7Zip extract, why? - powershell

I tried all suggestion on how to catch errors of 7Zip as explained in:
powershell - How to capture output in a variable rather than a logfile? - Stack Overflow
Redirecting output to $null in PowerShell, but ensuring the variable remains set - Stack Overflow
and played with try / catch.
Second contains only
Cannot find drive. A drive with the name ' 7-Zip 18.05 (x64) ' does
not exist.
in Error[0]
If I write the console output
7-Zip 18.05 (x64) : Copyright (c) 1999-2018 Igor Pavlov : 2018-04-30
Scanning the drive for archives:
1 file, 51273 bytes (51 KiB)
Extracting archive: \\...\850\DAY01
--
7z.exe : ERROR: Data Error : DAY01.RAW
At C:\Users\MyUser\Code\7Zip.ps1:6 char:1
+ & $7ZIP_FullPath x $IN_FullPath -o$OUT_Directory -y
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (ERROR: Data Error : DAY01.RAW:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Path = \\...\850\DAY01
Type = gzip
Headers Size = 20
Sub items Errors: 1
Archives with Errors: 1
Sub items Errors: 1
in a variable, the variable will contain only
7-Zip 18.05 (x64) : Copyright (c) 1999-2018 Igor Pavlov : 2018-04-30
Scanning the drive for archives:
1 file, 51273 bytes (51 KiB)
Extracting archive: \\...\850\DAY01
--
Path = \\...\850\DAY01
Type = gzip
Headers Size = 20
Sub items Errors: 1
Archives with Errors: 1
Sub items Errors: 1
It looks like, that
7z.exe : ERROR: Data Error : DAY01.RAW
At C:\Users\MyUser\Code\7Zip.ps1:6 char:1
+ & $7ZIP_FullPath x $IN_FullPath -o$OUT_Directory -y
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (ERROR: Data Error : DAY01.RAW:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
is created by PowerShell (what explaiuns 7z.exe : ERROR:..) and can only be captured if I use $out:
& $7ZIP_FullPath x $IN_FullPath -o$OUT_Directory -y > $out
It looks like $out triggers something but what?

7zip is not a PowerShell command so you can’t use the ordinary try{}catch{} with the -ErrorAction Stop
But 7zip has the ability to send out the %errorlevel% values in batch. Which is a better way to find an error
So According to the documentation these are the possible exit codes (%ERRORLEVEL% values):
Code Meaning
==== =======
0 No error
1 Warning (Non fatal error(s)). For example, one or more files were
locked by
some other application, so they were not compressed.
2 Fatal error
7 Command line error
8 Not enough memory for operation
255 User stopped the process
==== =======
In cmd or in a .bat file, there is no problem to run the 7zip command, and on the second line take out the %errorlevel% value, but in powershell you can’t really do that.
If you write the following code in powershell
To Zip:
& cmd.exe /c 'C:\"Program Files"\7-Zip\7z.exe a –mx1 "c:\DestinationFolder\NameOnZipFiles" c:\FolderToZip\*&echo:%errorlevel%'
To Unzip:
& cmd.exe /C 'C:\"Program Files"\7-Zip\7z.exe x "c:\SourceFolder" -o"D:\DestinationFolder" -y&echo:%errorlevel%'
You will notice that the %errorlevel% will always be 0, even if the command fails.
That’s because the %errorlevel% you get is the errorlevel before the command has run.
So to fix that problem you will have to enable the “DelayedExpansion” in cmd with “cmd.exe /v /c
the “%” will also have to be changed to “!” to use the delayed function.
So the correct command needs to be written like this to get the correct errorlevel
To Zip:
& cmd.exe /v /c 'C:\"Program Files"\7-Zip\7z.exe a –mx1 "c:\DestinationFolder\NameOnZipFiles" c:\FolderToZip\*&echo:!errorlevel!'
To Unzip:
& cmd.exe /v /C 'C:\"Program Files"\7-Zip\7z.exe x "c:\SourceFolder" -o"D:\DestinationFolder" -y&echo:!errorlevel!'
More info about the “DelayedExpansion” in cmd https://ss64.com/nt/delayedexpansion.html
Also If you only want to display the errorlevel after command has run, add the following 7zip options “-bso0 -bsp0 -bse0”
To Zip:
& cmd.exe /v /c 'C:\"Program Files"\7-Zip\7z.exe a –mx1 "c:\DestinationFolder\NameOnZipFiles" c:\FolderToZip\* -bso0 -bsp0 -bse0&echo:!errorlevel!'
To Unzip:
& cmd.exe /v /C 'C:\"Program Files"\7-Zip\7z.exe x "c:\SourceFolder" -o"D:\DestinationFolder" –y -bso0 -bsp0 -bse0&echo:!errorlevel!'
But in your case, if your only after having the error text in the same variable just add the 7zip option -bse1
$out=& $7ZIP_FullPath x $IN_FullPath -o$OUT_Directory -y -bse1

#daniel comments got me on the right track but still, I can not explain fully why only this solution is working.
$7ZIPExtractResult= & $7ZIP_FullPath x $IN_FullPath -o$OUT_Directory -y *>&1
Redirects the specified stream to the Success stream.
My guess is, that only that way it can be stored in a variable.
I found help in What does 2>&1 Mean in PowerShell - Stack Overflow and about_Redirection - PowerShell | Microsoft Docs.
In addition, the variable $7ZIPExtractResult is a System.Object[], so I have to do convert an array to a string doing 7ZIPExtractResultAsString = "$7ZIPExtractResult", expecting that is always possible and not throwing a different error.
It is still unknown to me why this error is not caught by
try {
}
catch {
}

Related

Pipe into executable through .lnk file

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.

How can I force powershell to output all errors normally in cmd?

I want to be able to use Sysnative cmd to execute a powershell script and output the results to a log.
When I run (System32):
C:\Windows\System32\cmd.exe /C "powershell -file “C:\…\ScriptWithErrors.ps1" >> C:\…\Log.log”
My log will show the errors from the powershell file (that usually show up in red in a terminal)
However when I run (Sysnative):
C:\Windows\Sysnative\cmd.exe /C "powershell -file “C:\…\ScriptWithErrors.ps1" >> C:\…\Log.log”
My log will not show any errors from the powershell script that I don’t explicitly use Write-Host $_.Exception for.
Is there a way to execute a powershell script and force the output of the errors to output?
Also, why does using cmd \C make echo not work properly?
>> C:\Windows\Sysnative\cmd.exe /C "echo time \t”
>> time /t (Output)
>> 11:20 AM (Expected output)
Powershell will save ALL errors for a session in the $Error variable.
The newest error will be number 0 in the array.
So basically to get all errors for a session you could just end up exporting the entire $Error variable array to your log, or foreach through it and get the parts you are interested in.
To get the line number of the error you could use the property ScriptStackTrace on the error variable.
For example I run:
Get-ChildItem -Path c:\cmder
Get-ChildItem -Path c:\nodirhere
The first line gives me the output, the second an error.
Now checking the newest error in the $Error array by selecting position 0 in the array and asking for the scriptstacktrace I get:
$Error[0].ScriptStackTrace
at <ScriptBlock>, <No file>: line 2

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 ...

Jenkins parametrized build - read file in powershell

I'm trying to create a Jenkins Parametrized build using the file parameter.
The user will select build with parameters, and will supply a text document with a list of items (one per line).
I would like to try and read this list in my powershell script to then go off and make some service calls (I've got that bit sussed).
I'm struggling with getting my powershell script to read the file that has been passed into Jenkins, here is what I have so far
$ids = Import-Csv input.txt
$array = #()
Write-Host $array.Length
foreach($id in $ids){
Write-Host $id.id
$array += $id
Write-Host "Array now has" $array.Length "items"
}
I know the script is running OK as I can run in Powershell ISE and pass in the absolute path of the csv/txt file.
For reference, the content of input.txt is like this:
id
2884430041011214,
9751297519392363,
lfsdkjgskdjflgsdjfg
Here are the two outputs I get for
Powershell ISE (as expected):
0
2884430041011214
Array now has 1 items
9751297519392363
Array now has 2 items
lfsdkjgskdjflgsdjfg
Array now has 3 items
Jenkins (not what I'm hoping for)
Started by user anonymous
Building in workspace C:\Users\jonec34\.jenkins\jobs\getParty\workspace
[workspace] $ powershell.exe -NonInteractive -ExecutionPolicy ByPass "& 'C:\Users\jonec34\AppData\Local\Temp\hudson4035619350462822370.ps1'"
Import-Csv : Could not find file 'C:\Users\jonec34\.jenkins\jobs\getParty\workspace\input.txt'.
At C:\Users\jonec34\AppData\Local\Temp\hudson4035619350462822370.ps1:1 char:8
+ $ids = Import-Csv input.txt
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (:) [Import-Csv], FileNotFoundException
+ FullyQualifiedErrorId : FileOpenFailure,Microsoft.PowerShell.Commands.ImportCsvCommand
0
Finished: SUCCESS
Can anyone tell me how I can do this please. I've also tried putting Param($file) at the start of the script, and using ${WORKSPACE} but either they're not working or I'm not understanding it properly. Any help greatly appreciated.
Thanks
Right, I've found where I was going wrong...
In my file parameter, I hadn't set a file location (EG I'd left it blank), but I entered in '\input.txt' and low and behold I get
Building in workspace C:\Users\jonec34\.jenkins\jobs\getParty\workspace
Copying file to \input.txt
[workspace] $ powershell.exe -NonInteractive -ExecutionPolicy ByPass "& 'C:\Users\jonec34\AppData\Local\Temp\hudson6216657038417634126.ps1'"
getting requests from file
0
2884430041011214
Array now has 1 items
9751297519392363
Array now has 2 items
lfsdkjgskdjflgsdjfg
Array now has 3 items
Finished: SUCCESS

Colon as string in parameters

Is there a way to exclude the colon (:), so you can print it via parameters as String?
Little example:
PowerShellTest.ps1:
param (
[string]$message="Error: No message defined!"
);
"Info: Test-Information"
$message;
if you now starts this script via powershell:
powershell.exe D:\PowerShellTest.ps1 -message "Error: Test-Error"
Now you get only the output string Error: the rest will be cut off
What do I have to do to get the whole string Error: Test-Error?
The problem is not the colon, but the space. You need to escape it using the backtick:
powershell.exe D:\PowerShellTest.ps1 -message 'Error:` Test-Error'
Use single quotes ' instead of double quotes " like so,
C:\temp>powershell c:\temp\test.ps1 -message 'Error: Test-Error'
Info: Test-Information
Error: Test-Error
Why are you running it that way? When you run powershell.exe from inside Powershell you are forcing the arguments to go through the old Windows cmd.exe style command line processing.
Instead you should just invoke your script directly:
PS C:\scripts> .\PowershellTest.ps1 -message "Error: Test-Error"
Info: Test-Information
Error: Test-Error
This way all of the arguments will pass right through without being converted to strings, having double quotes stripped and then being re-parsed.
If you get an error because running scripts is not permitted then the answer is to change your execution policy, not to start another copy of powershell.exe:
PS C:\scripts> .\PowershellTest.ps1 -message "Error: Test-Error"
.\PowershellTest.ps1 : File C:\scripts\PowershellTest.ps1 cannot be loaded because running scripts is disabled on this
system. For more information, see about_Execution_Policies at http://go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1
+ .\PowershellTest.ps1 -message "Error: Test-Error"
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : SecurityError: (:) [], PSSecurityException
+ FullyQualifiedErrorId : UnauthorizedAccess
If you see this error then start a Powershell window using the 'run as administrator' option and enter the command:
Set-ExecutionPolicy RemoteSigned
Close the administrator powershell and restart any other powershell windows you have running and now you should be able to run locally created scripts without any problems.