4

I'm trying to add XML validation to an existing Powershell script. I've written code in C# as a proof of concept, but when I port to Powershell and use the same input files, I'm getting different results. I'm using Powershell 3.0.

The C# code I have working is:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Schema;

namespace SchemaTest
{
    class Program
    {
        const string SCHEMA_PATH = @"C:\Test\test_schema.xsd";
        const string XML_PATH = @"C:\Test\test.xml";

        static void Main(string[] args)
        {
            XmlReader schemaReader = XmlReader.Create(SCHEMA_PATH);
            XmlSchema schema = XmlSchema.Read(
                schemaReader,
                (Object sender, ValidationEventArgs e) =>
                {
                    Console.Out.WriteLine("Error reading schema file");
                }
            );
            schemaReader.Close();

            XmlReaderSettings rSettings = new XmlReaderSettings();
            rSettings.ValidationType = ValidationType.Schema;
            rSettings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
            rSettings.Schemas.Add(schema);
            rSettings.ValidationEventHandler += (Object sender, ValidationEventArgs e) =>
            {
                Console.Out.WriteLine("Error validating XML file");
            };

            XmlReader configReader = XmlReader.Create(XML_PATH, rSettings);

            XmlDocument doc = new XmlDocument();
            doc.Load(configReader);
            configReader.Close();

            Console.ReadKey(true);
        }
    }
}

I have a simple XSD schema and corresponding (invalid) XML file, which produces the expected "Error validating XML file" message from the rSettings.ValidationEventHandler when run with this code.

The corresponding Powershell code I've written is as follows:

$schemaReader = [System.Xml.XmlReader]::Create('C:\Test\test_schema.xsd')
[System.Xml.Schema.ValidationEventHandler]$schemaValidationHandler = { Write-Output "Error reading schema file" }
$schema = [System.Xml.Schema.XmlSchema]::Read($schemaReader, $schemaValidationHandler)
$schemaReader.Close()

$rSettings = New-Object -Type System.Xml.XmlReaderSettings
$rSettings.ValidationType = [System.Xml.ValidationType]::Schema
$rSettings.ValidationFlags = $rSettings.ValidationFlags -bor [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings
$rSettings.Schemas.Add($schema)
Register-ObjectEvent -InputObject $rSettings -EventName ValidationEventHandler -Action { Write-Output 'Error validating XML file' }

$configReader = [System.Xml.XmlReader]::Create('C:\Test\test.xml', $rSettings)

$doc = New-Object -TypeName System.Xml.XmlDocument
$doc.Load($configReader)
$configReader.Close()

When I run this code against the same XSD and XML file, I don't get the "Error validating XML file" output as I expect.

To troubleshoot, I commented out the Register-ObjectEvent call, and verified that I am getting an exception when the $doc.Load($configReader) line is called. This leads me to think there is an issue with registering the event handler, but I can't seem to find an alternative.

For completeness, here are the XSD and XML files I'm using to test this:

test_schema.xsd:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.test.com"
xmlns="http://www.test.com"
elementFormDefault="qualified"
version="1.0.0">

   <xs:element name="root">
      <xs:complexType>
         <xs:sequence>
            <xs:element name="name" type="xs:string" />
            <xs:element name="age" type="xs:int" />
            <xs:element name="city" type="xs:string" />
         </xs:sequence>
         <xs:attribute name="id" type="xs:int" />
      </xs:complexType>
   </xs:element>
</xs:schema>

test.xml:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<root
 xmlns="http://www.test.com"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 id="1">
   <name>Joe</name>
<!--   Comment out <age> tags for testing
   <age>20</age>  
-->
   <city>New York City</city>
 </root>
3
  • I believe the problem is that you're using Write-Output in the event handler. If you try to change it to Write-Host (just for testing, not for keeping) you'll see that it gets written as expected. Commented Jan 28, 2014 at 20:37
  • @robert.westerlund That worked - I added the Write-Output because just quoting the string wasn't working...I didn't try the third (Write-Host) option. Why doesn't a Write-Output work in this case? Since there is nothing else in the pipeline, why doesn't it get written to Out-Default when it's in a script block, as explained in this post
    – TWReever
    Commented Jan 28, 2014 at 21:05
  • Write-Output is generally the one you should use to get output from a method (except if your method is named with the verb Show, you probably shouldn't use the Write-Host cmdlet at all), or Write-Verbose, Write-Debug, Write-Warning or Write-Error, depending on what kind of output we're talking about. I'm not sure how Write-Output works with events though (haven't read about it or tried myself, yet), but I can imagine that they are run in a different scope or something (especially since you really don't know when an eventhandler will be called). Commented Jan 28, 2014 at 22:38

1 Answer 1

1

In Powershell, event handlers are run as Jobs. As such, the results of their pipelines are not displayed on the screen the same as they would be in a pipeline in the main script. The results of a pipeline of an executing job is available using the Receive-Job cmdlet.

Since Write-Output has the effect of displaying objects to the default outstream, it will only actually display to the screen when called from the main script.

This chapter from the Powershell Cook book, does a decent job of explaining Powershell events and Jobs.

Not the answer you're looking for? Browse other questions tagged or ask your own question.