Can't call piped properties in a function. Powershell - function

So I'm trying to create a "download" function that uses a piped object property to determine a download method (sftp or http). Then either create an sftp script for putty/winscp or curl the http url. I am defining objects as follows:
#WinSCP
$winscp = new-object psobject
$winscp | add-member noteproperty name "WinSCP"
$winscp | add-member noteproperty dltype "http"
$winscp | add-member noteproperty file "winscp.exe"
$winscp | add-member noteproperty url "https://cdn.winscp.net/files/WinSCP-5.17.8-Setup.exe"
$winscp | add-member noteproperty path "$env:ProgramFiles(x86)\WinSCP"
$winscp | add-member noteproperty install 'msiexec /i "$DataPath\$winscp.file" /quiet /norestart'
#Database
$db = new-object psobject
$db | add-member noteproperty name "Client Database"
$db | add-member noteproperty dltype "sftp"
$db | add-member noteproperty file "database_"
$db | add-member noteproperty ver "check"
$db | add-member noteproperty ext ".csv"
$db | add-member noteproperty dir "db"
#DatabaseVersion
$db_ver = new-object psobject
$db_ver | add-member noteproperty name "Database Version File"
$db_ver | add-member noteproperty dltype "sftp"
$db_ver | add-member noteproperty file "current_version.txt"
$db_ver | add-member noteproperty dir "db"
Currently I'm having issues with the $Input variable within the function. It can only be used once and does not translate into an if statement. Since it contains an object with multiple properties, it needs converted to a new object within the function first I think. I'm new to powershell and haven't found a way of doing this yet. Here is the function I made and am trying to use:
function Download () {
#HTTP Download Method
if ($input.dltype -eq "http") {
curl $input.url -O $DataPath\$input.file
#HTTP Success or Error
$curlResult = $LastExitCode
if ($curlResult -eq 0)
{
Write-Host "Successfully downloaded $input.name"
}
else
{
Write-Host "Error downloading $input.name"
}
pause
}
#SFTP Download Method
if ($input.dltype -eq "sftp") {
sftpPassCheck
#Detect if version required
if ($input.ver = "check") {
#Download the objects version file
"$+$Input+_ver" | Download
#Update the object's ver property
$input.ver = [IO.File]::ReadAllText("$DataPath\current_version.txt")
#Build the new filename
$input.file = "$input.file"+"$input.ver"+"$input.ext"
#Delete the version file
Remove-Item "$DataPath\current_version.txt"
}
& "C:\Program Files (x86)\WinSCP\WinSCP.com" `
/log="$DataPath\SFTP.log" /ini=nul `
/command `
"open sftp://ftpconnector:$script:sftp_pass#$input.ip/ -hostkey=`"`"ssh-ed25519 255 SETvoRlAT0/eJJpRhRRpBO5vLfrhm5L1mRrMkOiPS70=`"`" -rawsettings ProxyPort=0" `
"cd /$input.dir" `
"lcd $DataPath" `
"get $input.file" `
"exit"
#SFTP Success or Error
$winscpResult = $LastExitCode
if ($winscpResult -eq 0)
{
Write-Host "Successfully downloaded $input.name"
}
else
{
Write-Host "Error downloading $input.name"
}
}
}
I'm probably missing something simple but I'm clueless at this point. Oh usage should be:
WinSCP | download

The proper way to bind input from the pipeline to a function's parameters is to declare an advanced function - see about_Functions_Advanced_Parameters and the implementation in the bottom section of this answer.
However, in simple cases a filter will do, which is a simplified form of a function that implicitly binds pipeline input to the automatic $_ variable and is called for each input object:
filter Download {
if ($_.dltype -eq "http") {
# ...
}
}
$input is another automatic variable, which in simple (non-advanced) functions is an enumerator for all pipeline input being received and must therefore be looped over.
That is, the following simple function is the equivalent of the above filter:
function Download {
# Explicit looping over $input is required.
foreach ($obj in $input) {
if ($obj.dltype -eq "http") {
# ...
}
}
}
If you do want to turn this into an advanced function (note that I've changed the name to conform to PowerShell's verb-noun naming convention):
function Invoke-Download {
param(
# Declare a parameter explicitly and mark it as
# as pipeline-binding.
[Parameter(ValueFromPipeline, Mandatory)]
$InputObject # Not type-constraining the parameter implies [object]
)
# The `process` block is called for each pipeline input object
# with $InputObject referencing the object at hand.
process {
if ($InputObject.dltype -eq "http") {
# ...
}
}
}

mklement0 is spot on - $input is not really meant to used directly, and you're probably much better off explicitly declaring your input parameters!
In addition to the $InputObject pattern shown in that answer, you can also bind input object property values to parameters by name:
function Download
{
param(
[Parameter(ValueFromPipelineByPropertyName = $true)]
[Alias('dltype')]
[string]$Protocol = 'http'
)
process {
Write-Host "Choice of protocol: $Protocol"
}
}
Notice that although the name of this parameter is $Protocol, the [Alias('dltype')] attribute will ensure that the value of the dltype property on the input object is bound.
The effect of this is:
PS ~> $WinSCP,$db |Download
Choice of protocol: http
Choice of protocol: sftp
Keep repeating this pattern for any required input parameter - declare a named parameter mapped to property names (if necessary), and you might end up with something like:
function Download
{
[CmdletBinding()]
param(
[Parameter(ValueFromPipelineByPropertyName = $true)]
[ValidateSet('sftp', 'http')]
[Alias('dltype')]
[string]$Protocol,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[Alias('dir')]
[string]$Path = $PWD,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('url','file')]
[string]$Uri
)
process {
Write-Host "Downloading $Uri to $Path over $Protocol"
}
}
Now you can do:
PS ~> $WinSCP,$db |Download
Downloading https://cdn.winscp.net/files/WinSCP-5.17.8-Setup.exe to C:\Program Files(x86)\WinSCP over http
Downloading database_ to db over sftp
We're no longer dependent on direct access to $input, $InputObject or $_, nice and clean.
Please see the about_Functions_Advanced_Parameters help file for more information about parameter declaration.

Related

How to parse HTML table with Powershell Core 7?

I have the following code:
$html = New-Object -ComObject "HTMLFile"
$source = Get-Content -Path $FilePath -Raw
try
{
$html.IHTMLDocument2_write($source) 2> $null
}
catch
{
$encoded = [Text.Encoding]::Unicode.GetBytes($source)
$html.write($encoded)
}
$t = $html.getElementsByTagName("table") | Where-Object {
$cells = $_.tBodies[0].rows[0].cells
$cells[0].innerText -eq "Name" -and
$cells[1].innerText -eq "Description" -and
$cells[2].innerText -eq "Default Value" -and
$cells[3].innerText -eq "Release"
}
The code works fine on Windows Powershell 5.1, but on Powershell Core 7 $_.tBodies[0].rows returns null.
So, how does one access the rows of an HTML table in PS 7?
PowerShell (Core), as of 7.3.1, does not come with a built-in HTML parser - and this may never change.
You must rely on a third-party solution, such as the PowerHTML module that wraps the HTML Agility Pack.
The object model works differently than the Internet Explorer-based one available in Windows PowerShell; it is similar to the XML DOM provided by the standard System.Xml.XmlDocument type ([xml])[1]; see the documentation and the sample code below.
# Install the module on demand
If (-not (Get-Module -ErrorAction Ignore -ListAvailable PowerHTML)) {
Write-Verbose "Installing PowerHTML module for the current user..."
Install-Module PowerHTML -ErrorAction Stop
}
Import-Module -ErrorAction Stop PowerHTML
# Create a sample HTML file with a table with 2 columns.
Get-Item $HOME | Select-Object Name, Mode | ConvertTo-Html > sample.html
# Parse the HTML file into an HTML DOM.
$htmlDom = ConvertFrom-Html -Path sample.html
# Find a specific table by its column names, using an XPath
# query to iterate over all tables.
$table = $htmlDom.SelectNodes('//table') | Where-Object {
$headerRow = $_.Element('tr') # or $tbl.Elements('tr')[0]
# Filter by column names
$headerRow.ChildNodes[0].InnerText -eq 'Name' -and
$headerRow.ChildNodes[1].InnerText -eq 'Mode'
}
# Print the table's HTML text.
$table.InnerHtml
# Extract the first data row's first column value.
# Note: #(...) is required around .Elements() for indexing to work.
#($table.Elements('tr'))[1].ChildNodes[0].InnerText
A Windows-only alternative is to use the HTMLFile COM object, as shown in this answer, and as used in your own attempt - I'm unclear on why it didn't work in your specific case.
[1] Notably with respect to supporting XPath queries via the .SelectSingleNode() and .SelectNodes() methods, exposing child nodes via a .ChildNodes collection, and providing .InnerHtml / .OuterHtml / .InnerText properties. Instead of an indexer that supports child element names, methods .Element(<name>) and .Elements(<name>) are provided.
I used the answer above for my solution. I installed PowerHTML.
I wanted to extract the datatable from https://www.dicomlibrary.com/dicom/dicom-tags/ and convert them.
From this:
<tr><td>(0002,0000)</td><td>UL</td><td>File Meta Information Group Length</td><td></td></tr>
To this:
{"00020000", "ULFile Meta Information Group Length"}
$page = Invoke-WebRequest https://www.dicomlibrary.com/dicom/dicom-tags/
$htmldom = ConvertFrom-Html $page
$table = $htmlDom.SelectNodes('//table') | Where-Object {
$headerRow = $_.Element('tr') # or $tbl.Elements('tr')[0]
# Filter by column names
$headerRow.ChildNodes[0].InnerText -eq 'Tag'
}
foreach ($row in $table.SelectNodes('tr'))
{$a = $row.SelectSingleNode('td[1]').innerText.Trim() -replace "`n|`r|\s+", " " -replace "\(",'{"' -replace ",","" -replace "\)",'",'
$c = $row.SelectSingleNode('td[3]').innerText.Trim() -replace "`n|`r|\s+", " "
$b=$row.seletSingleNode('td[2]').innerText.Trim() -replace "`n|`r|\s+", ""; $c = '"'+$b+$c+'"},'
$row = New-Object -TypeName psobject
$row | Add-Member -MemberType NoteProperty -Name Tag -Value $a
$row | Add-Member -MemberType NoteProperty -Name Value -Value $c
[array]$data += $row
}
$data | Out-File c:\scripts\dd.txt

PowerShell function not accepting array of objects

In PowerShell, I have an array of objects that I need to pass to a function. The function is to then loop through all of the objects in the array, but it seems that it is not accepting the parameter value correctly.
Take the following example, where I pass an array containing two objects. I would expect the count of the array to be 2 both before the function and within the function, but as soon as it hits the function the count is 1, and my input is not as expected; only the last object is discovered.
Am I missing something here, or is this a bug in PowerShell?
Example code
### I've also tries '[object]', '[array]' and '[array[]]' as the type for '$testArr'.
function Test-PassArrayOfObjects
{
param(
[parameter(Mandatory,ValueFromPipeline)]
[object[]]$testArr
)
Write-Host "In function count: $($testArr.Count)"
$testArr | ForEach-Object { $_ }
}
$test1 = New-Object –TypeName PSObject
$test1 | Add-Member -MemberType NoteProperty -Name Test1 -Value Value1
$test2 = New-Object –TypeName PSObject
$test2 | Add-Member -MemberType NoteProperty -Name Test2 -Value Value2
$testArr = #($test1, $test2)
$testArr.GetType() | Format-Table
Write-Host "Before function count: $($testArr.Count)"
$testArr | Test-PassArrayOfObjects
Output from example code
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Before function count: 2
In function count: 1
Test2
-----
Value2
Working fix
Based on the answer below, I have this working example, which I've been able to apply to my real life scenario.
function Test-PassArrayOfObjects
{
param(
[parameter(Mandatory,ValueFromPipeline)]
[object]$testArr
)
Process {
Write-Host "In function count: $($testArr.Count)"
$testArr
}
}
$test1 = New-Object –TypeName PSObject
$test1 | Add-Member -MemberType NoteProperty -Name Test1 -Value Value1
$test1 | Add-Member -MemberType NoteProperty -Name Test2 -Value Value2
$test2 = New-Object –TypeName PSObject
$test2 | Add-Member -MemberType NoteProperty -Name Test1 -Value Value1
$test2 | Add-Member -MemberType NoteProperty -Name Test2 -Value Value2
$testArr = #($test1, $test2)
$testArr.GetType() | Format-Table
Write-Host "Before function count: $($testArr.Count)"
$testArr | ForEach-Object { $_ | Test-PassArrayOfObjects }
When sending input to a function via the pipeline, your function should include a Process block:
function Test-PassArrayOfObjects
{
param(
[parameter(Mandatory,ValueFromPipeline)]
[object[]]$testArr
)
Process {
Write-Host "In function count: $($testArr.Count)"
$testArr | ForEach-Object { $_ }
}
}
This is necessary because of the way the pipeline handles collections I believe. It automatically unrolls them and handles them one item at a time, so your ForEach-Object isn't getting the whole collection in the $testArr variable.
You often see functions like this still incorporate a ForEach though, in case the input is sent via a parameter in which case it is received all at once. For example: Test-PassArrayOfObjects -TestArr #(1,2,3).
Your issue is further conflated by the fact that your array has two objects with different properties. This is creating confusion in the output because PowerShell decides how to format the output based on the first object and uses the same formatting when it outputs the second object, but then you don't see it because it doesn't share any of the same properties (I think this is what is occurring anyway..).
You can see that both objects get processed by putting | Format-List on the $_ which forces both outputs to be formatted as list output independently. Note that this isn't good practice in a real function scenario of course. An alternative is to make the property name on both objects Test1. Then you will see the output you probably expected without using Format-List.

ConvertTo-Json truncating object

I have a simple object with 1 parameter being an ArrayList of objects. I am using ConvertTo-Json to output this to Json. However even if I set -Depth 1000 I still see truncation of data.
Structure is:
Object
Property
Property - ArrayList of Object2.
Object 2 is a simple collection of properties.
The output I see is:
{
"CheckDate": "03 February 2016 10:12:30",
"Versions": [
{
},
{
}
]
}
Calling convert on the ArrayList directly all the data is shown. It would appear as if the -Depth argument is not being honored and is stuck at 2.
edit: Code to create object
$returnValue = New-Object System.Object
$returnValue | Add-Member -type NoteProperty -name CheckDate -value (Get-Date).DateTime
$versions = New-Object System.Collections.ArrayList
# This bit is in a loop.
$app = New-Object System.Object
$app | Add-Member -type NoteProperty -Name Name -Value $name
$app | Add-Member -type NoteProperty -Name Version -Value $version
$versions.Add($app)
# Back out of the loop.
$returnValue | Add-Member -type NoteProperty -name Versions -value $versions
Use PSObject instead of System.Object. Unfortunately, I cannot provide any details, it is some internal "magic" of ConvertTo-Json. Interestingly, it is enough to use PSObject instead of the second System.Object.

Enable Silverlight Plugin (NPAPI) in Chrome using registry key fix

I have created a Powershell script to add the npapi and Silverlight* registry keys to enable Silverlight in Google Chrome. The Powershell script works fine and adds the two registry keys, however the Silverlight plugin is still disabled in Chrome and when I load any Silverlight based sites I get the “Install Silverlight” popup. I have restarted the machine and still the Silverlight plugin is disabled.
However, if I go into the registry and delete just the npapi and Silverlight* registry keys and re-create them (String value - REG_SZ), when I reload the page in Chrome, Silverlight is now enabled and the site loads perfectly. I don’t understand what’s going on.
The powershell script creates these keys but only when I delete them and re-create them manually do they take effect and the Silverlight plugin is enabled. Then if I go into chrome://plugins, Chrome reports that the Silverlight plugin is “Enabled by Enterprise policy”. I have also run the script on another machine and the exact same thing happens. Has anyone else experienced this and does anyone know the fix or what I am doing wrong?
Powershell Script used to create the npapi and Silverlight* registry keys:
function Create-Path {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
[string]$Path
,
[Parameter(ValueFromPipeline = $true, Mandatory = $false)]
[switch]$OverwriteIfExists
)
process {
If(($OverwriteIfExists.IsPresent) -or (-not (Test-Path $Path))) {
New-Item $Path -Force | out-null
}
}
}
function Get-RegistryKeyWithValue {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
[string]$Path
,
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[string]$Value
)
process {
$properties = Get-Item $Path | select -ExpandProperty Property
$properties | %{
$property = Get-ItemProperty -Path $Path -Name $_
if ($property.$_ -eq $Value) {
write-output $property
}
}
}
}
function Get-NextKeyInPath {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
[string]$Path
)
process {
try {
write-output ((Get-Item $Path -ErrorAction Stop | select -ExpandProperty Property | Measure-Object -Maximum).Maximum + 1) | out-string
} catch {
write-output "1"
}
}
}
function Create-ChromeEnabledPluginPolicy {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[string]$Value
)
begin {
$ChromePluginPolicyPath = "HKLM:\SOFTWARE\Policies\Google\Chrome\EnabledPlugins"
Create-Path $ChromePluginPolicyPath
}
process {
if (-not (Get-RegistryKeyWithValue -Path $ChromePluginPolicyPath -Value $Value)) {
$keyName = Get-NextKeyInPath -Path $ChromePluginPolicyPath
New-ItemProperty -path $ChromePluginPolicyPath -Name $keyName -Value $Value -PropertyType String
}
}
}
"npapi", "Silverlight*" | Create-ChromeEnabledPluginPolicy
The code:
process {
try {
write-output ((Get-Item $Path -ErrorAction Stop | select -ExpandProperty Property | Measure-Object -Maximum).Maximum + 1) | out-string
} catch {
write-output "1"
}
}
Seems to return something more than a single string.
Amending to the following resolves the issue:
process {
try {
[int]$i = ((Get-Item $Path -ErrorAction Stop | select -ExpandProperty Property | Measure-Object -Maximum).Maximum + 1)
write-output ([string]$i)
} catch {
write-output "1"
}
}
A simplified demo of the issue & solution:
Run the following code: New-ItemProperty -Path 'HKLM:\SOFTWARE\JohnLBevan' -Name (1 | out-string) -PropertyType String -Value 'test'
Now open regedit and create a key with name 1; it succeeds (i.e. you have two keys called 1; so clearly some control/non displayable character is being added in our PS script).
If you try to add a third key called 1 using either method (regedit or powershell) you'll get an error due to a key with that name already existing (showing that there is a unique check in place; it's just our original 1s aren't unique)
If you try either of the following code snippets, things work as expected:
New-ItemProperty -Path'HKLM:\SOFTWARE\JohnLBevan' -Name "1" -PropertyType String -Value 'test'
New-ItemProperty -Path'HKLM:\SOFTWARE\JohnLBevan' -Name [string]1 -PropertyType String -Value 'test'
(Disclosure: I work with #jwoods83, so had the advantage of seeing the issue / playing with it directly)

Parsing text type log file

I have just started PowerShell today
I have this type of log files with any number of tests:
Plan test.pln - 1 error
[#VERSION-TestPlanGenerator#]3.8.0.0018
HW# VS4_1
[#TC#] test 1
\\APPS-EUAUTO1\C$\...\Temp\FXSAPIDebugLogFile.txt - The process cannot access the file because it is being used by another process.
[APPS-EUAUTO1] [prep] Setting agent options and random seed...
[APPS-EUAUTO1] [info] Initial Seed : 124426621
[APPS-EUAUTO1] [info] Current seed : 96010
[APPS-EUAUTO1] [info] rt1 t1
[APPS-EUAUTO1] [debug] rt1 t1
[#WARNING#][APPS-EUAUTO1] [warning] rt1 t1 ( Screen shot : D:\...\[APPS-EUAUTO1] 03-28-14 11-29-22.png)
[#WARNING#][APPS-EUAUTO1] [warning] Unhandled error detected ! ( Screen shot : D:\...\[APPS-EUAUTO1] 03-28-14 11-29-22.png)
[#ERROR#][APPS-EUAUTO1] [error] rt1 t1 ( Screen shot : D:...\[APPS-EUAUTO1] 03-28-14 11-29-22.png)
Occurred in fnMsg at ..\functions\f_common.inc(456)
Called from t1 at test.t(10)
Called from rt1 at test.t(5)
[#TC#] test 2
[APPS-EUAUTO1] [prep] Setting agent options and random seed...
[APPS-EUAUTO1] [info] Current seed : 177041
[APPS-EUAUTO1] [info] rt2 t2
[APPS-EUAUTO1] [debug] rt2 t2
I need to get all the tests in an array in witch each element will have:
a string Name ( ex: test 1)
a boolean Error (ex: true for test 1 because there is a [#WARNING#]
or a [#ERROR#] message present)
a array Messages with all the messages (ex: for test 2 all 4
messages)
and at the end I will like to export this array to a html file.
All tests begins with [#TC#].
I'm having problems with the reading part.
I have tried a couple of things from different sites but, it doesn't not seem to work for me :
Function Import-MyLog1 {
# -----------------------------------------------------------------------
Function Get-Log {
# Reads the log file into memory.
Try {
Get-Content -path "res.txt" -ErrorAction Stop -Delimiter "[#TC#]"
}
Catch {
Write-Error "The data file is not present"
BREAK
}
} # End: Function Get-Log
# -----------------------------------------------------------------------
Function Get-Record {
Param ($Log)
for ($i=1; $i -lt $Log.Length; $i++) { # ignore the header
$Testcase = $Log[$i]
New-Object PSobject -Property #{
Name = $Testcase[0]
Data = $Testcase[3..6]
}
}
} # End: Function Get-Record
# Load the log into memory
$Log = Get-Log
$Records = Get-Record -Log $Log
$Records # Added only to see the current progress.
} #End: Function Import-MyLog1
clear
Import-MyLog1
this is the final code for who might need a example :
Function Get-TxtLog {
Param ($File)
# Reads the log file into memory.
Try {
Get-Content -path $File -ErrorAction Stop -Delimiter "[#TC#]"
} Catch {
Write-Error "The data file is not present"
BREAK
}
} # End: Function Get-TxtLog
# -----------------------------------------------------------------------
Function Get-Testcase {
Param ($TxtLog)
for ($i=1; $i -lt $TxtLog.Count; $i++) { # $i=1 to ignore the header
$Testcase = $TxtLog[$i].split("`n")
$Output = New-Object PSobject -Property #{
Name = $Testcase[0].Trim()
Messages = $Testcase[1..($Testcase.count)] | ?{!($_ -match "\[#TC#]")} | ForEach-Object -process {$_.Trim()}
}
$Error = $( if($Output.Messages -match ("\[#ERROR#]|\[#WARNING#]")) {$true} else {$false} )
$Output|Add-Member -MemberType NoteProperty -Name "Error" -value $Error
$Output|Add-Member -MemberType NoteProperty -Name "Runtime" -value $null # will be added later
$Output|Add-Member -MemberType NoteProperty -Name "Data" -value $null # will be added later
$Output # to pipe the object
}
} # End: Function Get-Testcase
# -----------------------------------------------------------------------
# Load the log into memory
$TxtLog = Get-TxtLog -file "D:\XHostMachine\Results\res.txt"
$Records = Get-Testcase -TxtLog $TxtLog
$Records | Format-Table
If you just started PowerShell today then I can only imagine what you'll be doing with it in a year's time... You have started very well in my opinion.
Now, you seem to want to make everything into a function, which I suppose there is little harm in, but personally it seems overkill. When I pasted your test code into my ISE the first thing I did was comment out the first line and 28th line through the end of it. There just doesn't seem to be a need for all that.
Next I added a parameter to the Get-Log function so that a path can be provided if desired, and if excluded it will default to your res.txt file.
Function Get-Record {
Param ($Log)
for ($i=1; $i -lt $Log.Count; $i++) { # ignore the header
$Testcase = $Log[$i].split("`n")
$Output = New-Object PSobject -Property #{
Name = $Testcase[0]
Data = $Testcase[3..($Testcase.count)]|?{!($_ -match "\[#TC#]")}
}
$Output|Add-Member -MemberType NoteProperty -Name "Error" -value $(if($Output.data -match "^.+?(\[#ERROR#]|\[#WARNING#])"){$true}else{$false})
$Output
}
} # End: Function Get-Record
After that I looked at the value of $Log once it was gotten. You end up with an array with 3 strings in it. That's all fine and good, but what you really want is an array with 3 arrays in it if you ask me. Right now $Log[0] is a string with 4 lines of text, and you'd be better off with an array of 4 strings... so let's go that route first. I modified your Get-Record to accomplish that.
Function Get-Record {
Param ($Log)
for ($i=1; $i -lt $Log.Count; $i++) { # ignore the header
$Testcase = $Log[$i].split("`n")
You'll notice the split is done on n which is the powershell NewLine character. Then I updated the object you created to exclude the [#TC#] which was used as a delimiter, and assigned it a variable instead of just outputting it. Once I had that $Output variable I tested it for[#ERROR#]and[#WARNING#]` using a regex match and added a new Error property to the object depending on if an error was found or not.
$Output = New-Object PSobject -Property #{
Name = $Testcase[0]
Data = $Testcase[3..($Testcase.count)]|?{!($_ -match "\[#TC#]")}
}
$Output|Add-Member -MemberType NoteProperty -Name "Error" -value $(if($Output.data -match "^.+?(\[#ERROR#]|\[#WARNING#])"){$true}else{$false})
$Output
}
} # End: Function Get-Record
Then I pretty much passed the rest off as is except added my path to the log that I made from your example text.
# Load the log into memory
$Log = Get-Log c:\temp\test.log
$Records = Get-Record -Log $Log
$Records # Added only to see the current progress.
#} #End: Function Import-MyLog1
#
#clear
#Import-MyLog1
Now, you could clean it up a bit I suppose by trimming blank space from the beginning of lines if you wanted, but that's just a matter of taste. But it gives you 2 entries in $Records each with the name you wanted, and the data lines, and a boolean Error property.