Home > Software engineering >  Marshal and Unmarshal XML containing xmlns:wcm and xmlns:xsi correctly
Marshal and Unmarshal XML containing xmlns:wcm and xmlns:xsi correctly

Time:11-03

I'm trying to handle reading and writing of Windows Autounattend.xml files in order to create and modify them. I'm unable to get xmlns:wcm and xmlns:xsi attributes be marshaled and unmarshaled correctly.


<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="windowsPE">
        <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
            xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <SetupUILanguage>
                <UILanguage>en-US</UILanguage>
            </SetupUILanguage>
            <InputLocale>0409:00000409</InputLocale>
            <SystemLocale>en-US</SystemLocale>
            <UILanguage>en-US</UILanguage>
            <UILanguageFallback>en-US</UILanguageFallback>
            <UserLocale>en-US</UserLocale>
        </component>
        <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
            xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <DiskConfiguration>
                <Disk wcm:action="add">
                    <DiskID>0</DiskID>
                    <WillWipeDisk>true</WillWipeDisk>
                    <CreatePartitions>
                        <!-- Windows RE Tools partition -->
                        <CreatePartition wcm:action="add">
                            <Order>1</Order>
                            <Type>Primary</Type>
                            <Size>300</Size>
                        </CreatePartition>
                        <!-- System partition (ESP) -->
                        <CreatePartition wcm:action="add">
                            <Order>2</Order>
                            <Type>EFI</Type>
                            <Size>100</Size>
                        </CreatePartition>
                        <!-- Microsoft reserved partition (MSR) -->
                        <CreatePartition wcm:action="add">
                            <Order>3</Order>
                            <Type>MSR</Type>
                            <Size>128</Size>
                        </CreatePartition>
                        <!-- Windows partition -->
                        <CreatePartition wcm:action="add">
                            <Order>4</Order>
                            <Type>Primary</Type>
                            <Extend>true</Extend>
                        </CreatePartition>
                    </CreatePartitions>
                    <ModifyPartitions>
                        <ModifyPartition wcm:action="add">
                            <Order>1</Order>
                            <PartitionID>1</PartitionID>
                            <Label>WINRE</Label>
                            <Format>NTFS</Format>
                            <TypeID>DE94BBA4-06D1-4D40-A16A-BFD50179D6AC</TypeID>
                        </ModifyPartition>
                        <ModifyPartition wcm:action="add">
                            <Order>2</Order>
                            <PartitionID>2</PartitionID>
                            <Label>System</Label>
                            <Format>FAT32</Format>
                        </ModifyPartition>
                        <ModifyPartition wcm:action="add">
                            <Order>3</Order>
                            <PartitionID>3</PartitionID>
                        </ModifyPartition>
                        <ModifyPartition wcm:action="add">
                            <Order>4</Order>
                            <PartitionID>4</PartitionID>
                            <Label>Windows</Label>
                            <Letter>C</Letter>
                            <Format>NTFS</Format>
                        </ModifyPartition>
                    </ModifyPartitions>
                </Disk>
            </DiskConfiguration>
            <ImageInstall>
                <OSImage>
                    <InstallTo>
                        <DiskID>0</DiskID>
                        <PartitionID>4</PartitionID>
                    </InstallTo>
                    <InstallToAvailablePartition>false</InstallToAvailablePartition>
                </OSImage>
            </ImageInstall>
            <UserData>
                <ProductKey>
                    <Key></Key>
                    <WillShowUI>Never</WillShowUI>
                </ProductKey>
                <AcceptEula>true</AcceptEula>
                <FullName>admin</FullName>
                <Organization></Organization>
            </UserData>
        </component>
    </settings>
    <settings pass="offlineServicing">
        <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
            xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <EnableLUA>false</EnableLUA>
        </component>
    </settings>
    <settings pass="generalize">
        <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
            xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <SkipRearm>1</SkipRearm>
        </component>
    </settings>
    <settings pass="specialize">
        <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
            xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <InputLocale>0409:00000409</InputLocale>
            <SystemLocale>en-US</SystemLocale>
            <UILanguage>en-US</UILanguage>
            <UILanguageFallback>en-US</UILanguageFallback>
            <UserLocale>en-US</UserLocale>
        </component>
        <component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
            xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <SkipAutoActivation>true</SkipAutoActivation>
        </component>
        <component name="Microsoft-Windows-SQMApi" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
            xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <CEIPEnabled>0</CEIPEnabled>
        </component>
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
            xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <ComputerName>-PC</ComputerName>
            <ProductKey></ProductKey>
        </component>
    </settings>
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
            xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <AutoLogon>
                <Password>
                    <Value></Value>
                    <PlainText>true</PlainText>
                </Password>
                <Enabled>true</Enabled>
                <Username>admin</Username>
            </AutoLogon>
            <OOBE>
                <HideEULAPage>true</HideEULAPage>
                <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
                <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
                <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
                <NetworkLocation>Home</NetworkLocation>
                <SkipUserOOBE>true</SkipUserOOBE>
                <SkipMachineOOBE>true</SkipMachineOOBE>
                <ProtectYourPC>1</ProtectYourPC>
            </OOBE>
            <UserAccounts>
                <LocalAccounts>
                    <LocalAccount wcm:action="add">
                        <Password>
                            <Value></Value>
                            <PlainText>true</PlainText>
                        </Password>
                        <Description></Description>
                        <DisplayName>admin</DisplayName>
                        <Group>Administrators</Group>
                        <Name>admin</Name>
                    </LocalAccount>
                </LocalAccounts>
            </UserAccounts>
            <RegisteredOrganization></RegisteredOrganization>
            <RegisteredOwner>admin</RegisteredOwner>
            <DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet>
            <FirstLogonCommands>
                <SynchronousCommand wcm:action="add">
                    <Description>Control Panel View</Description>
                    <Order>1</Order>
                    <CommandLine>reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel" /v StartupPage /t REG_DWORD /d 1 /f</CommandLine>
                    <RequiresUserInput>true</RequiresUserInput>
                </SynchronousCommand>
                <SynchronousCommand wcm:action="add">
                    <Order>2</Order>
                    <Description>Control Panel Icon Size</Description>
                    <RequiresUserInput>false</RequiresUserInput>
                    <CommandLine>reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel" /v AllItemsIconView /t REG_DWORD /d 0 /f</CommandLine>
                </SynchronousCommand>
                <SynchronousCommand wcm:action="add">
                    <Order>3</Order>
                    <RequiresUserInput>false</RequiresUserInput>
                    <CommandLine>cmd /C wmic useraccount where name="admin" set PasswordExpires=false</CommandLine>
                    <Description>Password Never Expires</Description>
                </SynchronousCommand>
            </FirstLogonCommands>
            <TimeZone>Central Standard Time</TimeZone>
        </component>
    </settings>
</unattend>

I tried to use xml.Name,xml.Attr, and string for this with no luck


const (
    // A generic XML header suitable for use with the output of Marshal.
    Header = `<?xml version="1.0" encoding="UTF-8"?>`   "\n"
    WCM    = `http://schemas.microsoft.com/WMIConfig/2002/State`
    XSI    = `http://www.w3.org/2001/XMLSchema-instance`
)

type UserData struct {
    AcceptEula   bool   `xml:"AcceptEula"`
    FullName     string `xml:"FullName"`
    Organization string `xml:"Organization"`
    ProductKey   struct {
        Key        string `xml:"Key"`
        WillShowUI string `xml:"WillShowUI"`
    } `xml:"ProductKey"`
}

type OSImage struct {
    XMLName                     xml.Name `xml:"OSImage"`
    InstallToAvailablePartition bool     `xml:"InstallToAvailablePartition"`
    InstallToDiskID             uint32   `xml:"InstallTo>DiskID"`
    InstallToPatitionID         uint32   `xml:"InstallTo>ParitionID"`
}

type ImageInstall struct {
    XMLName xml.Name `xml:"ImageInstall"`
    OSImage OSImage  `xml:"OSImage"`
}

type CreatePartition struct {
    XMLName xml.Name `xml:"CreatePartition"`
    Order   uint32   `xml:"Order"`
    Type    string   `xml:"Type"`
    Size    uint64   `xml:"Size,omitempty"`
    Extend  bool     `xml:"Extend,omitempty"`
    Action  xml.Attr `xml:"action,attr"`
}
type ModifyPartition struct {
    XMLName     xml.Name `xml:"ModifyPartition"`
    Order       uint32   `xml:"Order"`
    PartitionID uint32   `xml:"PartitionID"`
    Label       string   `xml:"Label,omitempty"`
    Format      string   `xml:"Format,omitempty"`
    TypeID      string   `xml:"TypeID,omitempty"`
    Letter      string   `xml:"Letter,omitempty"`
    Action      xml.Attr `xml:"action,attr"`
}
type Disk struct {
    XMLName          xml.Name           `xml:"Disk"`
    Action           xml.Attr           `xml:"action,attr"`
    DiskID           uint32             `xml:"DiskID"`
    WillWipeDisk     bool               `xml:"WillWipeDisk"`
    CreatePartitions []*CreatePartition `xml:"CreatePartitions>."`
    ModifyPartitions []*ModifyPartition `xml:"ModifyPartitions>."`
}

type DiskConfiguration struct {
    XMLName xml.Name `xml:"DiskConfiguration"`
    Disks   []*Disk  `xml:"Disk"`
}

type SetupUILanguage struct {
    XMLName    xml.Name    `xml:"SetupUILanguage"`
    UILanguage *UILanguage `xml:"UILanguage"`
}

type UserLocale struct {
    XMLName xml.Name `xml:"UserLocale"`
    Value   string   `xml:",chardata"`
}

type InputLocale struct {
    XMLName xml.Name `xml:"InputLocale"`
    Value   string   `xml:",chardata"`
}

type SystemLocale struct {
    XMLName xml.Name `xml:"SystemLocale"`
    Value   string   `xml:",chardata"`
}

type UILanguage struct {
    XMLName xml.Name `xml:"UILanguage"`
    Value   string   `xml:",chardata"`
}

type UILanguageFallback struct {
    XMLName xml.Name `xml:"UILanguageFallback"`
    Value   string   `xml:",chardata"`
}

type EnableLUA struct {
    XMLName xml.Name `xml:"EnableLUA"`
    Value   bool     `xml:",chardata"`
}

type SkipRearm struct {
    XMLName xml.Name `xml:"SkipRearm"`
    Value   int32    `xml:",chardata"`
}

type SkipAutoActivation struct {
    XMLName xml.Name `xml:"SkipAutoActivation"`
    Value   bool     `xml:",chardata"`
}
type CEIPEnabled struct {
    XMLName xml.Name `xml:"CEIPEnabled"`
    Value   int32    `xml:",chardata"`
}
type ComputerName struct {
    XMLName xml.Name `xml:"ComputerName"`
    Value   string   `xml:",chardata"`
}
type ProductKey struct {
    XMLName xml.Name `xml:"ProductKey"`
    Value   string   `xml:",chardata"`
}

type Password struct {
    XMLName   xml.Name `xml:"Password"`
    Value     string   `xml:"Value"`
    PlainText bool     `xml:"PlainText"`
}

type AutoLogon struct {
    XMLName  xml.Name `xml:"AutoLogon"`
    Enabled  bool     `xml:"Enabled"`
    Username string   `xml:"Username"`
    Password Password `xml:"Password"`
}

type OOBE struct {
    XMLName                   xml.Name `xml:"OOBE"`
    HideEULAPage              bool     `xml:"HideEULAPage,omitempty"`
    HideOEMRegistrationScreen bool     `xml:"HideOEMRegistrationScreen,omitempty"`
    HideOnlineAccountScreens  bool     `xml:"HideOnlineAccountScreens,omitempty"`
    HideWirelessSetupInOOBE   bool     `xml:"HideWirelessSetupInOOBE,omitempty"`
    NetworkLocation           string   `xml:"NetworkLocation,omitempty"`
    SkipUserOOBE              bool     `xml:"SkipUserOOBE,omitempty"`
    SkipMachineOOBE           bool     `xml:"SkipMachineOOBE,omitempty"`
    ProtectYourPC             int32    `xml:"ProtectYourPC,omitempty"`
}

type LocalAccount struct {
    XMLName     xml.Name `xml:"LocalAccount"`
    Action      xml.Attr `xml:"action,attr"`
    Password    Password `xml:"Password"`
    Description string   `xml:"Description"`
    DisplayName string   `xml:"DisplayName"`
    Group       string   `xml:"Group"`
    Name        string   `xml:"Name"`
}

type LocalAccounts struct {
    XMLName      xml.Name
    LocalAccount []*LocalAccount `xml:"LocalAccount"`
}

type UserAccounts struct {
    XMLName       xml.Name      `xml:"UserAccounts"`
    LocalAccounts LocalAccounts `xml:"LocalAccounts"`
}

type RegisteredOrganization struct {
    XMLName xml.Name `xml:"RegisteredOrganization"`
    Value   string   `xml:",chardata"`
}
type RegisteredOwner struct {
    XMLName xml.Name `xml:"RegisteredOwner"`
    Value   string   `xml:",chardata"`
}
type DisableAutoDaylightTimeSet struct {
    XMLName xml.Name `xml:"DisableAutoDaylightTimeSet"`
    Value   bool     `xml:",chardata"`
}

type CommandLine struct {
    XMLName xml.Name `xml:"CommandLine"`
    Value   string   `xml:",innerxml"`
}

type SynchronousCommand struct {
    XMLName           xml.Name    `xml:"SynchronousCommand"`
    Order             uint32      `xml:"Order"`
    Description       string      `xml:"Description"`
    RequiresUserInput bool        `xml:"RequiresUserInput"`
    CommandLine       CommandLine `xml:"CommandLine"`
}

type FirstLogonCommands struct {
    XMLName            xml.Name              `xml:"FirstLogonCommands"`
    SynchronousCommand []*SynchronousCommand `xml:"SynchronousCommand"`
}
type TimeZone struct {
    XMLName xml.Name `xml:"TimeZone"`
    Value   string   `xml:",chardata"`
}

type Component struct {
    XMLName               xml.Name `xml:"component"`
    Name                  string   `xml:"name,attr"`
    ProcessorArchitecture string   `xml:"processorArchitecture,attr"`
    PublicKeyToken        string   `xml:"publicKeyToken,attr"`
    Language              string   `xml:"language,attr"`
    VersionScope          string   `xml:"versionScope,attr"`
    // optional compontents
    SetupUILanguage            *SetupUILanguage            `xml:"SetupUILanguage"`
    UserLocale                 *UserLocale                 `xml:"UserLocale"`
    InputLocale                *InputLocale                `xml:"InputLocale"`
    SystemLocale               *SystemLocale               `xml:"SystemLocale"`
    UILanguage                 *UILanguage                 `xml:"UILanguage"`
    UILanguageFallback         *UILanguageFallback         `xml:"UILanguageFallback"`
    DiskConfiguration          *DiskConfiguration          `xml:"DiskConfiguration,omitempty"`
    ImageInstall               *ImageInstall               `xml:"ImageInstall,omitempty"`
    UserData                   *UserData                   `xml:"UserData,omitempty"`
    EnableLUA                  *EnableLUA                  `xml:"EnableLUA,omitempty"`
    SkipRearm                  *SkipRearm                  `xml:"SkipRearm,omitempty"`
    ProductKey                 *ProductKey                 `xml:"ProductKey,omitempty"`
    ComputerName               *ComputerName               `xml:"ComputerName,omitempty"`
    SkipAutoActivation         *SkipAutoActivation         `xml:"SkipAutoActivation,omitempty"`
    CEIPEnabled                *CEIPEnabled                `xml:"CEIPEnabled,omitempty"`
    AutoLogon                  *AutoLogon                  `xml:"AutoLogon,omitempty"`
    OOBE                       *OOBE                       `xml:"OOBE,omitempty"`
    UserAccounts               *UserAccounts               `xml:"UserAccounts"`
    RegisteredOrganization     *RegisteredOrganization     `xml:"RegisteredOrganization,omitempty"`
    RegisteredOwner            *RegisteredOwner            `xml:"RegisteredOwner,omitempty"`
    DisableAutoDaylightTimeSet *DisableAutoDaylightTimeSet `xml:"DisableAutoDaylightTimeSet,omitempty"`
    FirstLogonCommands         *FirstLogonCommands         `xml:"FirstLogonCommands,omitempty"`
    TimeZone                   *TimeZone                   `xml:"TimeZone,omitempty"`
}

type Settings struct {
    XMLName    xml.Name     `xml:"settings"`
    Pass       string       `xml:"pass,attr"`
    Components []*Component `xml:"component"`
}

type Unattend struct {
    XMLName  xml.Name    `xml:"unattend"`
    XMLNS    xml.Attr    `xml:"xmlns,attr"`
    Settings []*Settings `xml:"settings"`
}


CodePudding user response:

xmlns:wcm and xmlns:xsi are not the real attributes, and they are processed in a special way. Your XML has neither elements nor attributes from wcm and xsi schemas, and they are dropped. Why do you need them in your code?

Still If you really-really need them, you can add the following field to the Component structure:

    Attr                  []xml.Attr `xml:",any,attr"`

xml.Unmarshal documentation says:

If the XML element has an attribute not handled by the previous rule and the struct has a field with an associated tag containing ",any,attr", Unmarshal records the attribute value in the first such field.

Here is an example: https://go.dev/play/p/tGDh5Ay1kZW

func main() {
    var u Unattend
    err := xml.Unmarshal([]byte(document), &u)
    if err != nil {
        log.Fatal(err)
    }
    for _, a := range u.Settings[0].Components[0].Attr {
        fmt.Printf("Unmatched attributes: %s:%s = %s\n", a.Name.Space, a.Name.Local, a.Value)
    }
}

Output:

Unmatched attributes: xmlns:wcm = http://schemas.microsoft.com/WMIConfig/2002/State
Unmatched attributes: xmlns:xsi = http://www.w3.org/2001/XMLSchema-instance

Note 1. What is not so good with this solution: it handles extra attributes in <component/> only. But xmlns:XXX attributes can appeare in any element. In general case you should add Attr array in all structures of your data model.

Note 2. The attributes xmlns:wcm and xmlns:xsi bear no business logic. They give no information regarding settings and components. Do you really need them?

Note 3. There is absolutely no requirement to name these attributes xmlns:wcm and xmlns:xsi. It is just a common practice. XML document can name them xmlns:foo and xmlns:bar - it is perfectly valid. Even the values "http://schemas.microsoft.com/WMIConfig/2002/State" and "http://www.w3.org/2001/XMLSchema-instance" have no meaning. They could be any string, the only requirement is to match URI format. E.g. urn:microsoft:wmi-config:2002:state is as valid as http://schemas.microsoft.com/WMIConfig/2002/State

Parsing those attributes in Component is very specific and could fail on olther valid XML documents that declare those attributes in other elements or with other suffixes or values.

UPDATE

Marshalling works not as expected. xml.Marshal handles xmlns namespace specifically and converts Attr for xmlns:wcm and xmlns:xsi into the namespace _xmlns

<component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:_xmlns="xmlns" _xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" _xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
</component>

The workaround is to use a special type for the attributes:

type XMLNSAttr struct {
    Name  xml.Name
    Value string
}

func (a XMLNSAttr) Label() string {
    if a.Name.Space == "" {
        return a.Name.Local
    }
    if a.Name.Local == "" {
        return a.Name.Space
    }
    return a.Name.Space   ":"   a.Name.Local
}

func (a *XMLNSAttr) UnmarshalXMLAttr(attr xml.Attr) error {
    a.Name = attr.Name
    a.Value = attr.Value
    return nil
}

func (a XMLNSAttr) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
    return xml.Attr{
        Name: xml.Name{
            Space: "",
            Local: a.Label(),
        },
        Value: a.Value,
    }, nil
}

This type does the trick by constructing an attribute with the namespace embedded into the local name. The field

    Attr                  []xml.Attr `xml:",any,attr"`

correctly decodes/encodes xmlns:xxx attributes

Example: https://go.dev/play/p/VDofREl5nf1

Output:

Unmatched attributes: xmlns:wcm = http://schemas.microsoft.com/WMIConfig/2002/State
Unmatched attributes: xmlns:xsi = http://www.w3.org/2001/XMLSchema-instance
<unattend>
  <settings pass="windowsPE">
    <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        ...
    </component>
    ...
  </settings>
</unattend>
  • Related