Advanced Powershell Error Handling for Microsoft Modules (eg Lync) - powershell

I have a fairly complex script that includes Lync functionality. For this I import the PSSession.
When one of these commands errors, my error handling returns the line and command within the Lync module it fails on (eg line 2055 $steppablePipeline.End() ) instead of the line and command in my code (eg line 18 Enable-CSUser)
Is there some way to capture my calling command and line?
The following is a simplified example of the script
Function Main
{
Try
{
LyncTest
MailboxTest
}
Catch {$rtn = ReturnErrorCatch $_ ; Return $rtn} #Return Failure
}
Function MailboxTest
{
$script:callline = (Get-PSCallStack)[1].ScriptLineNumber
Get-Mailbox "kfsjlxghkjsdh" -ErrorAction:Stop
}
Function LyncTest
{
$script:callline = (Get-PSCallStack)[1].ScriptLineNumber
Enable-CSUser "kfsjlxghkjsdh" -ErrorAction:Stop
}
Function ReturnErrorCatch ($catch)
{
If ($catch.InvocationInfo.InvocationName -eq "throw"){$errorreturn = $catch.Exception.Message.ToString()}
Else
{
$line = "Line: " + $catch.InvocationInfo.ScriptLineNumber.ToString()
$exception = $catch.Exception.Message.ToString() -replace "`n"," "
$command = "Command: " + ($catch.InvocationInfo.Line.ToString() -replace "`t","").Trim()
$activity = " (Activity: " + $catch.CategoryInfo.Activity +")"
$line = $line + "(" + $callline.ToString() + ")"
$errorreturn = $line + " " + $exception + "`r`n" + $command + $activity
}
$Output = New-Object PSObject -Property #{Result=1;ResultReason=$errorreturn}
Return $Output
}
$result = Main
$result | fl
This returns:
Result : 1
ResultReason : Line: 2055(5) If SIP address type (SipAddressType) is set to None or is not set, you must specify the SIP address (SipAddress).
Command: $steppablePipeline.End() (Activity: Enable-CsUser)
Within my script, I am able to get the calling line by putting the $script:callline line within every subfunction (better ways to do this?), so if I comment out the LyncTest, and run the mailboxtest, I get the expected error return with erroring command, the line it failed on, and the line the function was called from.
As this is for Exchange 2010, I cannot use any Powershell V3 functionality, it must be V2.

Related

Powershell Global Variable usage as parameter to argument

$global:af_fp = "C:\Path\to\folder\"
Function function-name {
do things …
$global:af_fp = $global:af_fp + $variableFromDo_things + "_AF.csv"
}
function-name | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
Above is the generalized (and abbreviated) script contents for a powershell script.
Every time I run the script in this way, I get the following error:
Add-Content : Could not find a part of the path 'C:\Users\timeuser\Documents\'.
At C:\Users\timeuser\Documents\get_software.ps1:231 char:51
+ ... ware | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\timeuser\Documents\:String) [Add-Content], DirectoryNotFoundException
+ FullyQualifiedErrorId : GetContentWriterDirectoryNotFoundError,Microsoft.PowerShell.Commands.AddContentCommand
When I run
Get-Variable -Scope global
after running the script and seeing the error, the variable af_fp contains exactly the information I am seeking for the file name, however, the error shows the variable contents ending in ':String'.
To confuse me even more, if I comment out the lines containing '$global:...' and re-run the same script, IT ACTUALL RUNS AND SAVES THE FILE USING THE LINE
function-name | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
AS INTENDED. Of course, I had to run the script and watch it error first, then re-run the script with the global variable declaration and update commented out for it to actually work. I want to run the script ONCE and still get the same results.
FYI, I am a complete noob to powershell, but very familiar with the concept of variable scope.....but why is this global not working when initially created and updated, but then work the second time around, when, as far as I can tell, the CONTENT AND SCOPE of the global remains the same...…. any assistance to finding a solution to this small issue would be greatly appreciated; I have tried sooooo may different methods from inquiries through here and on Google...…..
EDIT: not sure why this will matter, because the script ran before as intended when I explicitly typed the parameter for -Path as 'C:\path\to\file'. The ONLY CHANGES MADE to the original, working script (below) were my inclusion of the global variable declaration, the update to the contents of the global variable (near the end of the function), and the attempt to use the global variable as the parameter to -Path, that is why I omitted the script:
'''
$global:af_fp = "C:\Users\timeuser\Documents\"
Function Get-Software {
[OutputType('System.Software.Inventory')]
[Cmdletbinding()]
Param(
[Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[String[]]$Computername = $env:COMPUTERNAME
)
Begin {
}
Process {
ForEach ($Computer in $Computername) {
If (Test-Connection -ComputerName $Computer -Count 1 -Quiet) {
$Paths = #("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", "SOFTWARE\\Wow6432node\\Microsoft\\Windows\\CurrentVersion\\Uninstall")
ForEach ($Path in $Paths) {
Write-Verbose "Checking Path: $Path"
# Create an instance of the Registry Object and open the HKLM base key
Try {
$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine', $Computer, 'Registry64')
}
Catch {
Write-Error $_
Continue
}
# Drill down into the Uninstall key using the OpenSubKey Method
Try {
$regkey = $reg.OpenSubKey($Path)
# Retrieve an array of string that contain all the subkey names
$subkeys = $regkey.GetSubKeyNames()
# Open each Subkey and use GetValue Method to return the required values for each
ForEach ($key in $subkeys) {
Write-Verbose "Key: $Key"
$thisKey = $Path + "\\" + $key
Try {
$thisSubKey = $reg.OpenSubKey($thisKey)
# Prevent Objects with empty DisplayName
$DisplayName = $thisSubKey.getValue("DisplayName")
If ($DisplayName -AND $DisplayName -notmatch '^Update for|rollup|^Security Update|^Service Pack|^HotFix') {
$Date = $thisSubKey.GetValue('InstallDate')
If ($Date) {
Try {
$Date = [datetime]::ParseExact($Date, 'yyyyMMdd', $Null)
}
Catch {
Write-Warning "$($Computer): $_ <$($Date)>"
$Date = $Null
}
}
# Create New Object with empty Properties
$Publisher = Try {
$thisSubKey.GetValue('Publisher').Trim()
}
Catch {
$thisSubKey.GetValue('Publisher')
}
$Version = Try {
#Some weirdness with trailing [char]0 on some strings
$thisSubKey.GetValue('DisplayVersion').TrimEnd(([char[]](32, 0)))
}
Catch {
$thisSubKey.GetValue('DisplayVersion')
}
$UninstallString = Try {
$thisSubKey.GetValue('UninstallString').Trim()
}
Catch {
$thisSubKey.GetValue('UninstallString')
}
$InstallLocation = Try {
$thisSubKey.GetValue('InstallLocation').Trim()
}
Catch {
$thisSubKey.GetValue('InstallLocation')
}
$InstallSource = Try {
$thisSubKey.GetValue('InstallSource').Trim()
}
Catch {
$thisSubKey.GetValue('InstallSource')
}
$HelpLink = Try {
$thisSubKey.GetValue('HelpLink').Trim()
}
Catch {
$thisSubKey.GetValue('HelpLink')
}
$Object = [pscustomobject]#{
#Potential Candidate for AssetID in the TIME system
AssetID = $Computer
#String that contains word or word combinations for the product field of CPE WFN; may also contain the valid values necessary for update, edition, language, sw_edition, target_hw/sw fields as well.
cpeprodinfo = $DisplayName
cpeversion = $Version
InstallDate = $Date
cpevendor = $Publisher
UninstallString = $UninstallString
InstallLocation = $InstallLocation
InstallSource = $InstallSource
HelpLink = $thisSubKey.GetValue('HelpLink')
EstimatedSizeMB = [decimal]([math]::Round(($thisSubKey.GetValue('EstimatedSize') * 1024) / 1MB, 2))
}
$Object.pstypenames.insert(0, 'System.Software.Inventory')
Write-Output $Object
}
}
Catch {
Write-Warning "$Key : $_"
}
}
}
Catch { }
$reg.Close()
}
}
Else {
Write-Error "$($Computer): unable to reach remote system!"
}
$global:af_fp = $global:af_fp + $Computer + "_AF.csv"
}
}
}
Get-Software | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
'''
IGNORE FORMATTING PLEASE- HAD TROUBLE MAKING INDENTS CORRECTLY FROM COPY-PASTE AND RESTRICTIONS ON SITE FOR CODE BLOCKS.....
NOTE: the ONLY changes I made, that I am asking about, are the global declaration, the global variable update in the function, and the attempt to use the global variable for the -Path parameter....script otherwise runs and will even run WITH THE LAST LINE AS IS if I ran it and errored the first time.....not sure how the addition script will help in any way, shape, or form!
With a little effort, Nasir's solution worked! HOWEVER, I ran across a sample file that had a way of adding to a parameter that inspired me to make a change to my ORIGINAL, that also worked: remove global variable from script entirely and add this code the very end:
$file_suffix = '_AF.csv'
Get-Software | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $env:COMPUTERNAME$file_suffix
In this way, I was able to accomplish exactly what I was setting out to do! Thanks Nasir for your response as well! I was able to also make that work as intended!
Global variables are generally frowned upon, since they often lead to poor scripts, with hard to debug issues.
It seems like your function returns some stuff, which you need to write to a file, the name of which is also generated by the same function. You can try something like this:
function function-name {
param($PathPrefix)
#do things
[pscustomobject]#{"DoThings_data" = $somevariablefromDoThings; "Filename" = "$($PathPrefix)$($variableFromDo_Things)_AF.csv"}
}
function-name -PathPrefix "C:\Path\to\folder\" | Foreach-Object { $_.DoThings_data | Export-Csv -Path $_.Filename -NoTypeInformation }
Or just have your function write the CSV data out and then return the data if you need to further process it outside the function.
Edit: this is just me extrapolating from partial code you have provided. To Lee_Dailey's point, yes, please provide more details.

New-NetIPAddress argument "-IPAddress" causing "Missing closing '}' in statement block or type definition" error

I am trying to configure my local system's virtual switch from a config.json file that looks like this:
{
"system_name":"demo_system",
"version":"0.0",
"network_setting":{
"virtual_switch":{
"internal":{
"NATSwitch":{
"ip_address":"123.456.789.2",
"interface_alias":"vEthernet (NATSwitch)",
"internal_ip_interface_address_prefix":"123.456.789.0/24"
},
"Robot Switch":{
"ip_address":"987.654.321.2",
"interface_alias":"vEthernet (Robot Switch)",
"internal_ip_interface_address_prefix":"987.654.321.0/24"
}
}
}
}
}
The ps1 file testing.ps1 that reads this file and sets up the virtual switch is this:
Param
(
[string]$system_config = "C:\workspace\config.json"
)
function assert-virtual-switch
{
# $args[0] is the $networking_ht
Write-Output "Checking the virtual switches"
$ht = $args[0]
$internal_switch_psobject = $ht["virtual_switch"].internal
$internal_switch_ht = #{}
$internal_switch_psobject.psobject.properties | Foreach-Object { $internal_switch_ht[$_.Name] = $_.Value }
foreach ($internal_switch_key in $internal_switch_ht.Keys) {
if (Get-VMSwitch | Where-Object {$_.Name -eq $internal_switch_key}) {
# The switch already exist
Write-Output "Internal Switch $internal_switch_key exists"
}else{
# Create the virtual switch
Write-Output "Internal Switch $internal_switch_key doesn't exist, creating it now"
$internal_switch_ip = $internal_switch_ht[$internal_switch_key].ip_address
$interface_alias = $internal_switch_ht[$internal_switch_key].interface_alias
$internal_ip_interface_address_prefix = $internal_switch_ht[$internal_switch_key].internal_ip_interface_address_prefix
[byte]$prefix_length = 24
New-VMSwitch –SwitchName $internal_switch_key –SwitchType Internal
New-NetIPAddress -PrefixLength $prefix_length –IPAddress "$internal_switch_ip" -InterfaceAlias "$interface_alias"
New-NetNat –Name $internal_switch_key –InternalIPInterfaceAddressPrefix $internal_ip_interface_address_prefix
}
}
}
$sys_ps_object = Get-Content $system_config | Out-String | ConvertFrom-Json
$sys_ht = #{}
$sys_ps_object.psobject.properties | Foreach-Object { $sys_ht[$_.Name] = $_.Value }
# At this point, the system config is converted to a hash table, we can read from it like such:
foreach($sys_config_key in $sys_ht.Keys){
if ($sys_config_key -eq "network_setting"){
# $networking_ht is a hashtabe of the networking setting
$networking_ps_object = $sys_ht[$sys_config_key]
$networking_ht = #{}
$networking_ps_object.psobject.properties | Foreach-Object { $networking_ht[$_.Name] = $_.Value }
}
}
assert-virtual-switch $networking_ht
As you can see, I've created a function assert-virtual-switch that makes sure the virtual switch exists locally and will create one if it doesn't exist. The input of this function is a hash table of the internal switch information extracted from config.json.
However, I've been running into this error:
Error
At C:\workspace\test\test_new_ipaddress\testing.ps1:39 char:69
+ ... "External switch $external_switch_key doesn't exist, creating it now"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
The string is missing the terminator: '.
At C:\workspace\test\test_new_ipaddress\testing.ps1:20 char:14
+ }else{
+ ~
Missing closing '}' in statement block or type definition.
At C:\workspace\test\test_new_ipaddress\testing.ps1:16 char:64
+ foreach ($internal_switch_key in $internal_switch_ht.Keys) {
+ ~
Missing closing '}' in statement block or type definition.
At C:\workspace\test\test_new_ipaddress\testing.ps1:6 char:1
+ {
+ ~
Missing closing '}' in statement block or type definition.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
I've pinpointed the problem to this line that creates and configures the ip address.
New-NetIPAddress -PrefixLength $prefix_length –IPAddress "$internal_switch_ip" -InterfaceAlias "$interface_alias"
If I don't include the -IPAddress argument like this:
New-NetIPAddress -PrefixLength $prefix_length -InterfaceAlias "$interface_alias"
the error doesn't show up anymore:
PS C:\workspace> .\testing.ps1
Checking the virtual switches
Internal Switch Robot Switch exists
Internal Switch NATSwitch exists
So it must be coming from how the cmdlet interprets the IP address since I've tried with and without the quotation mark around $internal_switch_ip.
On another note, if I run the code line by line directly in the Powershell Console, it works.
Does anyone have any ideas of why this happens?
EDITS
As per the solution #Lee_Daily provided, the problem was caused by having a mix of dash and em-dash. After I changed all my "–" to "-", the code works.

How to fix powershell expression which causes error later in the code

I am trying to follow this example in order to attach images to an email with powershell. Here is the part of the code that behaves strange:
if ($DirectoryInfo) {
foreach ($element in $DirectoryInfo) {
$failedTest = $element| Select-Object -Expand name
$failedTests += $failedTest
$failedTestLog = "$PathLog\$failedTest.log"
$logContent = [IO.File]::ReadAllText($failedTestLog)
$imageDir = "$PathLog\$element\Firefox\*"
$imageSearch = Get-ChildItem -Path $imageDir -Include *.png -Recurse -Force
$imageFullname = $imageSearch | select FullName | Select-Object -Expand Fullname
$imageFilename = $imageSearch | Select-Object -Expand name
$imageFilename
$imageFullname
# *** THE FOLLOWING LINE CAUSES THE ERROR ***
$attachment = New-Object System.Net.Mail.Attachment –ArgumentList $imageFullname.ToString() # *** CAUSING ERROR ***
#$attachment.ContentDisposition.Inline = $True
#$attachment.ContentDisposition.DispositionType = "Inline"
#$attachment.ContentType.MediaType = "image/jpg"
#$attachment.ContentId = '$imageFilename'
#$msg.Attachments.Add($attachment)
$outputLog += "
********************************************
$failedTest
********************************************
$logContent
"
}
} else {
$outputLog = '** No failed tests **'
}
# Create the Overview report
$outputSummary = ""
foreach ($element in $scenarioInfo) {
if (CheckTest $failedTests $element) {
$outputSummary += "
$element : FAILED" # *** ERROR LINE ***
} Else {
$outputSummary += "
$element : Passed"
}
}
If I comment out the line which defines the attachment, the code works fine. If I use the code as it is, I get the following error:
Unexpected token ':' in expression or statement.
At D:\Testing\Data\Powershell\LoadRunner\LRmain.ps1:112 char:11
+ $element : <<<< FAILED"
+ CategoryInfo : ParserError: (::String) [], ParseException
+ FullyQualifiedErrorId : UnexpectedToken
which refers to the line at the bottom of the script where it says "ERROR LINE". What the heck is going on? The behavior look completely illogical to me! I don't understand how a statement, which has no effect at all, can cause an error elsewhere! What is the problem and how to fix it...?
Also it does not matter if I use $imageFullname or $imageFullname.ToString() in the offending line.
Try to replace "$element : FAILED" by
"$element` : FAILED"
The reverse quote will escape the semicolon; which has a specific meaning in PowerShell. (It allows to output subproperty : $env:username for example)
Define the $outputSummary as Array:
$outputSummary = #()
instead of
$outputSummary = ""

Why does PowerShell chops message on stderr?

I'm using a PowerShell script to control different compilation steps of an compiler (ghdl.exe).
The compiler has 3 different output formats:
No output and no error => $LastExitCode = 0
outputs on stderr (warnings), but no errors => $LastExitCode = 0
outputs on stderr (errors), and maybe warnings => $LastExitCode != 0
Because handling of stderr and stdout seams to be very buggy, I used the method presented in this StackOverflow post: PowerShell: Manage errors with Invoke-Expression
Here is my implementation with addition message coloring:
function Format-NativeCommandStreams
{ param([Parameter(ValueFromPipeline=$true)]$InputObject)
begin
{ $ErrorRecordFound = $false }
process
{ if (-not $InputObject)
{ Write-Host "Empty" }
elseif ($InputObject -is [System.Management.Automation.ErrorRecord])
{ $ErrorRecordFound = $true
$text = $InputObject.ToString()
Write-Host $text -ForegroundColor Gray
$stdErr = $InputObject.TargetObject
if ($stdErr)
{ #Write-Host ("err: type=" + $stdErr.GetType() + " " + $stdErr)
if ($stdErr.Contains("warning"))
{ Write-Host "WARNING: " -NoNewline -ForegroundColor Yellow }
else
{ Write-Host "ERROR: " -NoNewline -ForegroundColor Red }
Write-Host $stdErr
}
}
else
{ $stdOut = $InputObject
if ($stdOut.Contains("warning"))
{ Write-Host "WARNING: " -NoNewline -ForegroundColor Yellow }
else
{ Write-Host "ERROR: " -NoNewline -ForegroundColor Red }
Write-Host $stdOut
}
}
end
{ $ErrorRecordFound }
}
Usage:
$Options = #(.....)
$Expr = "ghdl.exe -a " + ($Options -join " ") + " " + $File + " 2>&1"
$ret = Invoke-Expression $Expr | Format-NativeCommandStreams
Normally, the compiler emits one message (error or warning) per line. As shown in the screenshot below, some messages got chopped in up to 8 lines. That's the reason why my output coloring does not work as expected. More over some lines are detected as errors (false positives), so I can't find the real error in the logs.
(clickable)
Example:
C:\Altera\15.0\quartus\eda\sim_lib\altera_mf.vhd:
39963:
53
:
warning:
universal integer bound must be numeric literal or attribute
C:\Altera\15.0\quartus\eda\sim_lib\altera_mf.vhd
:41794:36:warning: universal integer bound must be numeric literal or attribute
Expected Result:
C:\Altera\15.0\quartus\eda\sim_lib\altera_mf.vhd:39963:53:warning: universal integer bound must be numeric literal or attribute
C:\Altera\15.0\quartus\eda\sim_lib\altera_mf.vhd:41794:36:warning: universal integer bound must be numeric literal or attribute
As far as I can see, the compiler (ghdl.exe) does emit the messages as full lines.
Questions:
Why does this happen?
Who can I solve this?
Solution
The complete output on stderr of the executable is simply split across several objects of type System.Management.Automation.ErrorRecord. The actual splitting seems to be non deterministic (*). Moreover, the partial strings are stored inside the property Exception instead of TargetObject. Only the first ErrorRecord has a non-null TargetObject. That is, why subsequent lines of your output containing the string "warning" are not formatted in yellow and white, like this one:
:41794:36:warning: universal integer bound must be numeric literal or attribute
Your grey output comes from the toString() method of each ErrorRecord which returns the value of the property Exception.Message of this record.
So one must concatenate all messages together to get the whole output before formatting it. Newlines are preserved in these messages.
EDIT: (*) It depends on the order of write/flush calls of the program in relation to the read calls of the Powershell. If one adds a fflush(stderr) after each fprintf() in my test program below, there will be much more ErrorRecord objects. Except the first one, which seems deterministic, some of them include 2 output lines and some of them 3.
My testbench
Instead of using GHDL I started with a new Visual Studio project and created a console application (HelloWorldEx) with the following code. It simply prints out a lot of numbered lines on stderr
#include "stdafx.h"
#include <stdio.h>
int _tmain(int argc, _TCHAR* argv[])
{
// Print some warning messages on stderr
for(int i=0; i<70; i++) {
fprintf(stderr, "warning:%070d\n", i); // 80 bytes per line including CR+LF
}
return 0; // exit code is not relevant
}
Then I compiled the program and executed it inside the Powershell with:
(EDIT: removed debug code from my own script)
.\HelloWorldEx.exe 2>&1 | set-variable Output
$i = 0
$Output | % {
Write-Host ("--- " + $i + ": " + $_.GetType() + " ------------------------")
Write-Host ($_ | Format-List -Force | Out-String)
$i++
}
This was the output of the script. As you can see, the output of my program is split accross 3 ErrorRecords (the actual might differ):
--- 0: System.Management.Automation.ErrorRecord ------------------------
writeErrorStream : True
Exception : System.Management.Automation.RemoteException: warning:00000000000000000000000000000000000000000
00000000000000000000000000000
TargetObject : warning:0000000000000000000000000000000000000000000000000000000000000000000000
CategoryInfo : NotSpecified: (warning:0000000...000000000000000:String) [], RemoteException
FullyQualifiedErrorId : NativeCommandError
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
PipelineIterationInfo : {0, 0, 0}
PSMessageDetails :
--- 1: System.Management.Automation.ErrorRecord ------------------------
writeErrorStream : True
Exception : System.Management.Automation.RemoteException: warning:00000000000000000000000000000000000000000
00000000000000000000000000001
warning:0000000000000000000000000000000000000000000000000000000000000000000002
warning:0000000000000000000000000000000000000000000000000000000000000000000003
warning:0000000000000000000000000000000000000000000000000000000000000000000004
warning:0000000000000000000000000000000000000000000000000000000000000000000005
warning:0000000000000000000000000000000000000000000000000000000000000000000006
warning:0000000000000000000000000000000000000000000000000000000000000000000007
warning:0000000000000000000000000000000000000000000000000000000000000000000008
warning:0000000000000000000000000000000000000000000000000000000000000000000009
warning:0000000000000000000000000000000000000000000000000000000000000000000010
warning:0000000000000000000000000000000000000000000000000000000000000000000011
warning:0000000000000000000000000000000000000000000000000000000000000000000012
warning:0000000000000000000000000000000000000000000000000000000000000000000013
warning:0000000000000000000000000000000000000000000000000000000000000000000014
warning:0000000000000000000000000000000000000000000000000000000000000000000015
warning:0000000000000000000000000000000000000000000000000000000000000000000016
warning:0000000000000000000000000000000000000000000000000000000000000000000017
warning:0000000000000000000000000000000000000000000000000000000000000000000018
warning:0000000000000000000000000000000000000000000000000000000000000000000019
warning:0000000000000000000000000000000000000000000000000000000000000000000020
warning:0000000000000000000000000000000000000000000000000000000000000000000021
warning:0000000000000000000000000000000000000000000000000000000000000000000022
warning:0000000000000000000000000000000000000000000000000000000000000000000023
warning:0000000000000000000000000000000000000000000000000000000000000000000024
warning:0000000000000000000000000000000000000000000000000000000000000000000025
warning:0000000000000000000000000000000000000000000000000000000000000000000026
warning:0000000000000000000000000000000000000000000000000000000000000000000027
warning:0000000000000000000000000000000000000000000000000000000000000000000028
warning:0000000000000000000000000000000000000000000000000000000000000000000029
warning:0000000000000000000000000000000000000000000000000000000000000000000030
warning:0000000000000000000000000000000000000000000000000000000000000000000031
warning:0000000000000000000000000000000000000000000000000000000000000000000032
warning:0000000000000000000000000000000000000000000000000000000000000000000033
warning:0000000000000000000000000000000000000000000000000000000000000000000034
warning:0000000000000000000000000000000000000000000000000000000000000000000035
warning:0000000000000000000000000000000000000000000000000000000000000000000036
warning:0000000000000000000000000000000000000000000000000000000000000000000037
warning:0000000000000000000000000000000000000000000000000000000000000000000038
warning:0000000000000000000000000000000000000000000000000000000000000000000039
warning:0000000000000000000000000000000000000000000000000000000000000000000040
warning:0000000000000000000000000000000000000000000000000000000000000000000041
warning:0000000000000000000000000000000000000000000000000000000000000000000042
warning:0000000000000000000000000000000000000000000000000000000000000000000043
warning:0000000000000000000000000000000000000000000000000000000000000000000044
warning:0000000000000000000000000000000000000000000000000000000000000000000045
warning:0000000000000000000000000000000000000000000000000000000000000000000046
warning:0000000000000000000000000000000000000000000000000000000000000000000047
warning:0000000000000000000000000000000000000000000000000000000000000000000048
warning:0000000000000000000000000000000000000000000000000000000000000000000049
warning:0000000000000000000000000000000000000000000000000000000000000000000050
warning:00000000000000000000000000000000000000000000000000000000000
TargetObject :
CategoryInfo : NotSpecified: (:) [], RemoteException
FullyQualifiedErrorId : NativeCommandErrorMessage
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
PipelineIterationInfo : {0, 0, 1}
PSMessageDetails :
--- 2: System.Management.Automation.ErrorRecord ------------------------
writeErrorStream : True
Exception : System.Management.Automation.RemoteException: 00000000051
warning:0000000000000000000000000000000000000000000000000000000000000000000052
warning:0000000000000000000000000000000000000000000000000000000000000000000053
warning:0000000000000000000000000000000000000000000000000000000000000000000054
warning:0000000000000000000000000000000000000000000000000000000000000000000055
warning:0000000000000000000000000000000000000000000000000000000000000000000056
warning:0000000000000000000000000000000000000000000000000000000000000000000057
warning:0000000000000000000000000000000000000000000000000000000000000000000058
warning:0000000000000000000000000000000000000000000000000000000000000000000059
warning:0000000000000000000000000000000000000000000000000000000000000000000060
warning:0000000000000000000000000000000000000000000000000000000000000000000061
warning:0000000000000000000000000000000000000000000000000000000000000000000062
warning:0000000000000000000000000000000000000000000000000000000000000000000063
warning:0000000000000000000000000000000000000000000000000000000000000000000064
warning:0000000000000000000000000000000000000000000000000000000000000000000065
warning:0000000000000000000000000000000000000000000000000000000000000000000066
warning:0000000000000000000000000000000000000000000000000000000000000000000067
warning:0000000000000000000000000000000000000000000000000000000000000000000068
warning:0000000000000000000000000000000000000000000000000000000000000000000069
TargetObject :
CategoryInfo : NotSpecified: (:) [], RemoteException
FullyQualifiedErrorId : NativeCommandErrorMessage
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
PipelineIterationInfo : {0, 0, 2}
PSMessageDetails :
You can a bit of debugging to sort this out. I suggest starting with something like this:
ghdl.exe <whatever args you supply> 2>&1 | set-variable ghdlOutput
$i = 0
$ghdlOutput | % {write-host "$i `t: " $_.gettype() "`t" $_ ; $i++}
This will list the line number, type of the output line, and each live of the output. You may have to tweak the code some to get the output to look OK.
From there you can see if the compiler is really splitting up errors into multiple lines. If it is you can try devise a strategy for determining which lines are stdout and which are stderr. If not, then you'll have some clues to debugging your script above.
Or can bag this whole approach and use the .NET system.diagnostics.process class and redirect stdout and stderr as separate streams. Use the Start method that takes a ProcessStartInfo. You should be able to google examples of doing this if you need to.
Just for completeness, here are my current CommandLets, which restore the error messages as a single line and color them as wanted:
Usage:
$InvokeExpr = "ghdl.exe " + ($Options -join " ") + " --work=unisim " + $File.FullName + " 2>&1"
$ErrorRecordFound = Invoke-Expression $InvokeExpr | Collect-NativeCommandStream | Write-ColoredGHDLLine
CommandLet to restore the error messages:
function Collect-NativeCommandStream
{ [CmdletBinding()]
param([Parameter(ValueFromPipeline=$true)]$InputObject)
begin
{ $LineRemainer = "" }
process
{ if (-not $InputObject)
{ Write-Host "Empty pipeline!" }
elseif ($InputObject -is [System.Management.Automation.ErrorRecord])
{ if ($InputObject.FullyQualifiedErrorId -eq "NativeCommandError")
{ Write-Output $InputObject.ToString() }
elseif ($InputObject.FullyQualifiedErrorId -eq "NativeCommandErrorMessage")
{ $NewLine = $LineRemainer + $InputObject.ToString()
while (($NewLinePos = $NewLine.IndexOf("`n")) -ne -1)
{ Write-Output $NewLine.Substring(0, $NewLinePos)
$NewLine = $NewLine.Substring($NewLinePos + 1)
}
$LineRemainer = $NewLine
}
}
elseif ($InputObject -is [String])
{ Write-Output $InputObject }
else
{ Write-Host "Unsupported object in pipeline stream" }
}
end
{ }
}
CommandLet to color warnings and errors:
function Write-ColoredGHDLLine
{ [CmdletBinding()]
param([Parameter(ValueFromPipeline=$true)]$InputObject)
begin
{ $ErrorRecordFound = $false }
process
{ if (-not $InputObject)
{ Write-Host "Empty pipeline!" }
elseif ($InputObject -is [String])
{ if ($InputObject.Contains("warning"))
{ Write-Host "WARNING: " -NoNewline -ForegroundColor Yellow }
else
{ $ErrorRecordFound = $true
Write-Host "ERROR: " -NoNewline -ForegroundColor Red
}
Write-Host $InputObject
}
else
{ Write-Host "Unsupported object in pipeline stream" }
}
end
{ $ErrorRecordFound }
}
It seems that I managed to solve the problem with Martin Zabel example and the solution turned out to be quite prosaic and simple.
The fact is that for a long time I could not get the characters `r`n from an incoming call. And it turned out to be simple.
Replacing the `r with `n is the only thing that needed to be done at all!
The solution will work correctly for any width of the console, because the reverses have been removed.
Also, this may be the basis for solving the problem of returning processed data to the console in real time. The only thing that is needed is to catch the incoming single `r or `n to get a new "variable-string" in and send the processed data back to the console with `r or `n, depending on the task .
cls
function GetAnsVal {
param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Object[]][AllowEmptyString()]$Output,
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$firstEncNew="UTF-8",
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$secondEncNew="CP866"
)
function ConvertTo-Encoding ([string]$From, [string]$To){#"UTF-8" "CP866" "ASCII" "windows-1251"
Begin{
$encFrom = [System.Text.Encoding]::GetEncoding($from)
$encTo = [System.Text.Encoding]::GetEncoding($to)
}
Process{
$Text=($_).ToString()
$bytes = $encTo.GetBytes($Text)
$bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
$encTo.GetString($bytes)
}
}
$all = New-Object System.Collections.Generic.List[System.Object];
$exception = New-Object System.Collections.Generic.List[System.Object];
$stderr = New-Object System.Collections.Generic.List[System.Object];
$stdout = New-Object System.Collections.Generic.List[System.Object]
$i = 0;$Output | % {
if ($_ -ne $null){
if ($_.GetType().FullName -ne 'System.Management.Automation.ErrorRecord'){
if ($_.Exception.message -ne $null){$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$exception.Add($Temp)}
elseif ($_ -ne $null){$Temp=$_ | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$stdout.Add($Temp)}
} else {
#if (MyNonTerminatingError.Exception is AccessDeniedException)
$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;
$all.Add($Temp);$stderr.Add($Temp)
}
}
$i++
}
[hashtable]$return = #{}
$return.Meta0=$all;$return.Meta1=$exception;$return.Meta2=$stderr;$return.Meta3=$stdout;
return $return
}
Add-Type -AssemblyName System.Windows.Forms;
& C:\Windows\System32\curl.exe 'api.ipify.org/?format=plain' 2>&1 | set-variable Output;
$r = & GetAnsVal $Output
$Meta0=""
foreach ($el in $r.Meta0){
$Meta0+=$el
}
$Meta0=($Meta0 -split "[`r`n]") -join "`n"
$Meta0=($Meta0 -split "[`n]{2,}") -join "`n"
[Console]::Write($Meta0);
[Console]::Write("`n");

PowerShell Split-Job empty pipe element not allowed

I would really like to use the function Split-Job to throttle or run the same script block in parallel, so it goes quicker. This is especially useful when I need to launch multiple times the same copy command on different input.
The code can be found here and always spits out the following error:
An empty pipe element is not allowed.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : EmptyPipeElement
I know the problem is within this function, but I can't seem to solve it:
function Init ($InputQueue){
# Create the shared thread-safe queue and fill it with the input objects
$Queue = [Collections.Queue]::Synchronized([Collections.Queue]#($InputQueue))
$QueueLength = $Queue.Count
# Do not create more runspaces than input objects
if ($MaxPipelines -gt $QueueLength) {$MaxPipelines = $QueueLength}
# Create the script to be run by each runspace
$Script = "Set-Location '$PWD'; "
$Script += {
$SplitJobQueue = $($Input)
& {
trap {continue}
while ($SplitJobQueue.Count) {$SplitJobQueue.Dequeue()}
} |
}.ToString() + $Scriptblock
# Create an array to keep track of the set of pipelines
$Pipelines = New-Object System.Collections.ArrayList
# Collect the functions and aliases to import
$ImportItems = ($Function -replace '^','Function:') +
($Alias -replace '^','Alias:') |
Get-Item | select PSPath, Definition
$stopwatch = New-Object System.Diagnostics.Stopwatch
$stopwatch.Start()
}
Thank you for your help. Because this little function might help me out a lot if it would just work.
In this section:
$Script += {
$SplitJobQueue = $($Input)
& {
trap {continue}
while ($SplitJobQueue.Count) {$SplitJobQueue.Dequeue()}
} |
}.ToString() + $Scriptblock
There doesn't appear to be any reason for that pipe to be there.