0

I am attempting to mock up a data structure and logic to handle deferred log writing, and I am running into an odd situation. if ([collections.arrayList]$deferredLog = Get-PxDeferredLogItems) { errors when the returned arrayList is only one item, with

Cannot convert the "logged: 07/20/2019 10:56:29" value of type "System.String" to type "System.Collections.ArrayList".

Once there are two items it's fine. Now I know an array of a single item needs to be forced to be an array because PS is sometimes too clever for it's own good, but I thought the Add method of a [collections.arrayList] produced an arrayList even on the first Add. AM I totally misunderstanding, or is my logic wrong somewhere else? I also tried casting the results of the function call like this if ([collections.arrayList]$deferredLog = [collections.arrayList](Get-PxDeferredLogItems)) { but that is even worse, it errors at every call. I also tried initializing the variable to an empty arrayList with Set-PxDeferredLogItems (New-Object collections.arrayList) in Main, rather the n initializing it on the first call of Add-PxDeferredLogItem, but that shows the same behavior, errors until the deferred log has two items. I verified this by changing the Write-Host directly after the erroring line to Write-Host "Process deferred items $($deferredLog.count)", and I get errors till it shows a 2, then everything works as expected.

function Get-PxDeferredLogItems {
    return $script:deferredLogItems
}
function Set-PxDeferredLogItems {
    param (
        [collections.arrayList]$deferredLog
    )
    [collections.arrayList]$script:deferredLogItems = $deferredLog
}

function Add-PxDeferredLogItem {
    param (
        [string]$item
    )
    if (-not $script:deferredLogItems) {
        [collections.arrayList]$script:deferredLogItems = New-Object collections.arrayList
    }
    [void]$script:deferredLogItems.Add($item)
}

function Add-PxLogContent {
    param (
        [string]$string
    )

    function Assert-PxWrite {
        if ((Get-Random -minimum:1 -maximum:4) -gt 1) {
            return $true
        } else {
            return $false
        }
    }

    if ([collections.arrayList]$deferredLog = Get-PxDeferredLogItems) {
        Write-Host "Process deferred items"
        :deferredItemsWrite do {
            if (Assert-PxWrite) {
                Write-Host "$($deferredLog[0]) ($(Get-Date))"
                $deferredLog.RemoveAt(0)
            } else {
                break :deferredItemsWrite
            }
        } while ($deferredLog.count -gt 0)
        if ($deferredLog.count -eq 0) {
            $deferredLogPending = $false
        } else {
            $deferredLogPending = $true
        }
        Set-PxDeferredLogItems $deferredLog
    } else {
        $deferredLogPending = $false
    }

    if (-not $deferredLogPending) {
        if (Assert-PxWrite) {
            Write-Host $string
        } else {
            Add-PxDeferredLogItem $string
        }
    } else {
        Add-PxDeferredLogItem $string
    }
}


### MAIN 
$startTime = Get-Date
$endTime = $startTime + (New-TimeSpan -minutes:2)
Clear-Host

do {
    Add-PxLogContent "logged: $(Get-Date)"
    Start-SLeep -s:5
} while ((Get-Date) -lt $endTime) 

if ($deferredLog = Get-PxDeferredLogItems) {
    foreach ($item in $deferredLog) {
        Write-Host "!$item"
    }
}

EDIT: It seems to be related to passing the arrayList, as this works as expected when there are no functions involved.

Clear-Host

$deferredLogItems = New-Object collections.arrayList
Write-Host "$($deferredLogItems.getType()) $($deferredLogItems.count) $deferredLogItems"
[void]$deferredLogItems.Add('One')
Write-Host "$($deferredLogItems.getType()) $($deferredLogItems.count) $deferredLogItems"
[void]$deferredLogItems.Add('Two')
Write-Host "$($deferredLogItems.getType()) $($deferredLogItems.count) $deferredLogItems"
[void]$deferredLogItems.Add('Three')
Write-Host "$($deferredLogItems.getType()) $($deferredLogItems.count) $deferredLogItems"


function Get-DeferredLogItems {
    if (-not $script:deferredLogItems) {
        [collections.arrayList]$script:deferredLogItems = New-Object collections.arrayList
    }
    [void]$script:deferredLogItems.Add("$($script:deferredLogItems.count + 1)")
    return $script:deferredLogItems
}

$script:deferredLogItems = $localDeferredLogItems = $null
foreach ($i in 1..5) {
    [collections.arrayList]$localDeferredLogItems = Get-DeferredLogItems
    Write-Host "$($localDeferredLogItems.getType()) $($localDeferredLogItems.count) $localDeferredLogItems"
}

EDIT2: Replaced the line # reference with the actual offending line of code, because Confusion.

EDIT3: For what it's worth, forcing the results of Get-DeferredLogItems into an array DOES work. But I still wonder why that's even needed. Why does the Add method produce a correct arrayList at first add but this gets @%#$ed up by PowerShell when passed as a return value?

Gordon
  • 6,257
  • 6
  • 36
  • 89
  • 1
    for those who are curious about it, the following is line 34 >>> ` if ([collections.arrayList]$deferredLog = Get-PxDeferredLogItems) {` << – Lee_Dailey Jul 20 '19 at 09:32
  • @Lee_Daily I'm confused. I am adding with [void]$script:deferredLogItems.Add($item). [collections.arrayList]$deferredLog = Get-PxDeferredLogItems is simply getting the value of the script scope variable back and assigning it to a function scope variable. At least, that's what I think I'm doing. :) – Gordon Jul 20 '19 at 09:38
  • 2
    `return $script:deferredLogItems` -> `,$script:deferredLogItems` – user4003407 Jul 20 '19 at 09:57
  • @PetSerAl, that's a good way to force the returned value into an array once, at the return of the function rather than (potentially) multiple times at the use of the function. But I am still confused as to why it's needed. Why is powershell jacking up my types? or is this just "one of those powershell things" like crapping into the Pipeline with meaningless junk? For the most part I love PS, but some of the design decisions that make sense for one liners need to be suppressible for longer scripts. :( – Gordon Jul 20 '19 at 10:25
  • 1
    TD;TR, anyways, this is known PowerShell behavior and explained in depth on several sites, and StackOverflow q&A's. As a possible solution also look into: [`Write-Output -NoEnumerate $script:deferredLogItems](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/write-output?view=powershell-6#parameters) – iRon Jul 20 '19 at 10:47
  • 1
    Possible duplicate of [In what conditions does powershell unroll items in the pipeline?](https://stackoverflow.com/questions/28702588/in-what-conditions-does-powershell-unroll-items-in-the-pipeline) and [Why does Powershell silently convert a string array with one item to a string](https://superuser.com/q/414650) – iRon Jul 20 '19 at 11:02

0 Answers0