0

I am refactoring some function based XML reader code to class methods, and seeing some issues. With the function, I can run a test and verify the XML loaded right, then change the XML and test for error conditions. But this class based approach fails due to "the file is open in another program", forcing me to close the console before I can revise the XML. Initially I was using the path directly in the xmlReader. So I moved to a StreamReader input to the xmlReader. And I even played with creating an all new xmlDocument and importing the root node of the loaded XML into that new xmlDocument. None works. I suspect the reason the function based version works is because the xmlReader variable is local scope, so it goes out of scope when the function completes. But I'm grasping at straws there. I also read that Garbage Collection could be an issue, so I added [system.gc]::Collect() right after the Dispose and still no change.

class ImportXML {
    # Properties
    [int]$status = 0
    [xml.xmlDocument]$xml = ([xml.xmlDocument]::New())
    [collections.arrayList]$message = ([collections.arrayList]::New())

    # Methods
    [xml.xmlDocument] ImportFile([string]$path) {
        $importError = $false
        $importFile = ([xml.xmlDocument]::New())
        $xmlReaderSettings = [xml.xmlReaderSettings]::New()
        $xmlReaderSettings.ignoreComments = $true
        $xmlReaderSettings.closeInput = $true
        $xmlReaderSettings.prohibitDtd = $false
        try {
            $streamReader = [io.streamReader]::New($path)
            $xmlreader = [xml.xmlreader]::Create($streamReader, $xmlReaderSettings)
            [void]$importFile.Load($xmlreader)
            $xmlreader.Dispose
            $streamReader.Dispose
        } catch {
            $exceptionName = $_.exception.GetType().name
            $exceptionMessage = $_.exception.message
            switch ($exceptionName) {
                Default {
                    [void]$this.message.Add("E_$($exceptionName): $exceptionMessage")
                    $importError = $true
                }
            }
        }

        if ($importError) {
            $importFile = $null
        }

        return $importFile
    }
}

class SettingsXML : ImportXML {
    # Constructor
    SettingsXML([string]$path){
        if ($this.xml = $this.ImportFile($path)) {
            Write-Host "$path!"
        } else {
            Write-Host "$($this.message)"
        }
    }
}

$settingsPath = '\\Mac\iCloud Drive\Px Tools\Dev 4.0\Settings.xml'
$settings = [SettingsXML]::New($settingsPath)

EDIT: I also tried a FileStream rather than a StreamReader, with FileShare of ReadWrite, like so

$fileMode = [System.IO.FileMode]::Open
$fileAccess = [System.IO.FileAccess]::Read
$fileShare = [System.IO.FileShare]::ReadWrite
$fileStream = New-Object -TypeName System.IO.FileStream $path, $fileMode, $fileAccess, $fileShare

Still no luck.

1 Answer 1

2

I think you're on the right lines with Dispose, but you're not actually invoking the method - you're just getting a reference to it and then not doing anything with it...

Compare:

PS> $streamReader = [io.streamReader]::New(".\test.xml");
PS> $streamReader.Dispose

OverloadDefinitions
-------------------
void Dispose()
void IDisposable.Dispose()
PS> _

with

PS> $streamReader = [io.streamReader]::New(".\test.xml");
PS> $streamReader.Dispose()
PS> _

You need to add some () after the method name so your code becomes:

$xmlreader.Dispose()
$streamReader.Dispose()

And then it should release the file lock ok.

Sign up to request clarification or add additional context in comments.

10 Comments

Also the dispose call should be in a finally block, not in the try block.
@Tomalak - and you'd need to check the objects are not null in the catch before you dispose them in case the error happened before they both got instantiated (e.g. $path doesn't exist) :-)
Aha! And.... dang it! I really am just slow in groking this move to classes. Because as soon as you said that, I remembered a completely unrelated issue where I was referencing the method as an object rather than calling the method, and getting something back that wasn't what I expected. Gah. Hopefully I don't need a third time before it's a charm. ;)
@mclayton Absolutely: try { ... } catch { ... } finally { if ($xmlreader) { $xmlreader.Dispose() } }.
This does beg the question, if one wanted to work with an XML file, using a streamReader because the file is huge, and manage the sharing of that file while working with it, one would need to start with a fileStream to manage access, and feed that info a streamReader to handle the file size, and feed that into an xmlReader to get the XML functionality, correct? Or is a fileStream already going to handle the file size? In which case, when would one ever just use a streamReader over a fileStream? Or is it the other pair, & an xmlReader is also a streamReader, so I can send the fileStream direct?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.