1

xml sample

<?xml version="1.0" encoding="UTF-8"?>
<CUSTOMERS xml:lang="en">
    <CUSTOMER CREATED_DATE="2020-10-16 13:21:09.0" GROUP_ID="" ID="1509999">
        <NAME>
            <TITLE></TITLE>
            <FIRST_NAME>John</FIRST_NAME>
            <LAST_NAME>Smith</LAST_NAME>
        </NAME>
        <GENDER/>
        <DATE_OF_BIRTH/>
        <CONTACT_DETAILS>
            <TELEPHONE MARKETING_OPT_IN="F" TYPE="MOBILE">0123456789</TELEPHONE>
            <EMAIL MARKETING_OPT_IN="F">[email protected]</EMAIL>
        </CONTACT_DETAILS>
        <ATTRIBUTE NAME="theCompany-News and offers on theCompany Womenswear_OPT_EMAIL">T</ATTRIBUTE>
    <ATTRIBUTE NAME="Email Soft Opt In_OPT_EMAIL">F</ATTRIBUTE>
        <ATTRIBUTE NAME="REGISTERED_ON_WEBSITE">T</ATTRIBUTE>
    </CUSTOMER>
   </CUSTOMERS>

I have inherited an Azure Powershell function which builds a list of values from XML file. I need to add some code to access this value:

<ATTRIBUTE NAME="Email Soft Opt In_OPT_EMAIL">F</ATTRIBUTE>

This is the Powershell Code I have so far

[xml]$xml = Get-Content C:\Users\Jason2\Desktop\XMLfolder\theSample.xml
$xml
$CustomerListFull = @()

foreach ($customer in $xml.CUSTOMERS.CUSTOMER) 
{           $BuildList = New-Object -TypeName psobject
            $BuildList | Add-Member -MemberType NoteProperty -Name TITLE -Value $customer.NAME.TITLE.Trim()
            $BuildList | Add-Member -MemberType NoteProperty -Name FIRSTNAME -Value $customer.NAME.FIRST_NAME.Trim()
        $BuildList | Add-Member -MemberType NoteProperty -Name LASTNAME -Value $customer.NAME.LAST_NAME.Trim()
            $BuildList | Add-Member -MemberType NoteProperty -Name EMAILCONSENT -Value "0"
            $BuildList | Add-Member -MemberType NoteProperty -Name EMAILSOFTOPTIN -Value "2"

    if ($customer.ATTRIBUTE.NAME -like "*OPT_EMAIL") {
                foreach ($value in $customer.ATTRIBUTE.NAME) {
                    
                        if ($value -match "theCompany-News and offers on theCompany Womenswear_OPT_EMAIL") {
                            $BuildList.EMAILCONSENT = "1"
                        }
                        if ($value -match "Email Soft Opt In_OPT_EMAIL") {
                        $BuildList.EMAILSOFTOPTIN = $customer.ATTRIBUTE.'#text'
                        }
               }
            }                 

$CustomerListFull += $BuildList
}
$CustomerListFull

which is incorrectly giving me all three Attribute Name values (into field EMAILSOFTOPTIN)

TITLE          :
FIRSTNAME      : John
LASTNAME       : Smith
EMAILCONSENT   : 1
EMAILSOFTOPTIN : {T, F, T}

I have tried several things to try and access the value, user marsze yesterday showed me nodes which is great but I can't seem to make it work in my forloop, I'm failing miserably.

$BuildList.EMAILSOFTOPTIN = $customer.SelectNodes("//*[local-name()='ATTRIBUTE'][@NAME='Email Soft Opt In_OPT_EMAIL']").InnerText

$BuildList.EMAILSOFTOPTIN = $customer.[ATTRIBUTE].[NAME='Email Soft Opt In_OPT_EMAIL').InnerText

$nodes = $xml.SelectNodes("//*[local-name()='ATTRIBUTE'][@NAME='Email Soft Opt In_OPT_EMAIL']")
$BuildList.EMAILSOFTOPTIN = $nodes.InnerText

Please help to put me out of my misery

1 Answer 1

1

There are multiple tiny flaws in your code.

1.)

$customer.ATTRIBUTE.NAME -like "*OPT_EMAIL"

If the left operand is a collection, the result is also a collection, namely of all the items that return true for the operation, in this case all values of NAME that match *OPT_EMAIL". Also, this check is unnecessary, because you make another literal comparison later.

2.)

$value -match "Email Soft Opt In_OPT_EMAIL"

-Match is probably not a good choice here, because it treats the right value as regex. It will work in this case, but I think you want -eq

3.)

$customer.ATTRIBUTE.'#text'

This will always return a collection of the inner text of all <ATTRIBUTE> nodes of the current customer, namely {T, F, T}

===

Here is my version, with some additional changes, but that is just a matter of personal taste:

[xml]$xml = Get-Content "C:\Users\Jason2\Desktop\XMLfolder\theSample.xml"
# you can build the array directly, like this
# because the += operator on arrays is terribly slow
$customerListFull = @(foreach ($customer in $xml.Customers.Customer) {
    # defaults
    $emailConsent = 0 # or $false?
    $emailSoftOptIn = "2"
    # loop attributes
    # the "OPT_EMAIL" check is dispensable here
    foreach ($attribute in $customer.Attribute) {
        # switch is a good replacement for a lot of if's,
        # that basically all do the same check
        switch ($attribute.Name) {
            "theCompany-News and offers on theCompany Womenswear_OPT_EMAIL" {
                $emailConsent = 1 # or $true?
            }
            "Email Soft Opt In_OPT_EMAIL" {
                $emailSoftOptIn = switch ($attribute.InnerText.Trim()) {
                    "F" { 0 }
                    "T" { 1 }
                }
                ### OR ###
                $emailSoftOptIn = @{F = 0; T = 1}[$attribute.InnerText.Trim()]
            }
        }
    }
    # Create and output the object directly.
    # This is an easier way to construct an object,
    # which also keeps the property order
    [PSCustomObject]@{
        Title = $customer.Name.Title.Trim()
        FirstName = $customer.Name.First_Name.Trim()
        LastName = $customer.Name.Last_Name.Trim()
        EmailConsent = $emailConsent
        EmailSoftOptIn = $emailSoftOptIn
    }
    ### OR ###
    # use [ordered]
    New-Object -TypeName PSObject -Property ([ordered]@{
        Title = $customer.Name.Title.Trim()
        FirstName = $customer.Name.First_Name.Trim()
        LastName = $customer.Name.Last_Name.Trim()
        EmailConsent = $emailConsent
        EmailSoftOptIn = $emailSoftOptIn
    })
})
Sign up to request clarification or add additional context in comments.

7 Comments

wow thank you marsze this is a real education for me, I'm going to take a look now and digest, looks great
@RogerClerkwell Thanks, yes I took the freedom to make these additional changes to give you a chance to learn and improve. Also, please note how proper indentation and casing help with readability a lot, and prevent mistakes.
Hi marsze, works really well, a couple of things I want to transform the 'F' and 'T' to 0 and 1, if I add IF like below to the script is this good practise or I should do another way.
"Email Soft Opt In_OPT_EMAIL" { if($attribute.InnerText.Trim() -eq 'F') {$emailSoftOptIn = "0" } if($attribute.InnerText.Trim() -eq 'T') {$emailSoftOptIn = "1" }
simply superb answers with examples, it works perfectly. many thanks marsze I have learnt a lot from this post today
|

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.