Spagett
Spagett

Reputation: 147

XML edit - value is null

Running into an issue with a null valued expression which I can't seem to figure out.

The script is supposed to change the value of the node <ComputerName> in an unattend.xml file, but returns an error.

XML file:

<?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>0c09:00000409</InputLocale>
      <SystemLocale>en-US</SystemLocale>
      <UILanguage>en-US</UILanguage>
      <UILanguageFallback>en-US</UILanguageFallback>
      <UserLocale>en-AU</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">
          <CreatePartitions>
            <CreatePartition wcm:action="add">
              <Order>1</Order>
              <Type>Primary</Type>
              <Size>100</Size>
            </CreatePartition>
            <CreatePartition wcm:action="add">
              <Extend>true</Extend>
              <Order>2</Order>
              <Type>Primary</Type>
            </CreatePartition>
          </CreatePartitions>
          <ModifyPartitions>
            <ModifyPartition wcm:action="add">
              <Active>true</Active>
              <Format>NTFS</Format>
              <Label>System Reserved</Label>
              <Order>1</Order>
              <PartitionID>1</PartitionID>
              <TypeID>0x27</TypeID>
            </ModifyPartition>
            <ModifyPartition wcm:action="add">
              <Active>true</Active>
              <Format>NTFS</Format>
              <Label>OS</Label>
              <Letter>C</Letter>
              <Order>2</Order>
              <PartitionID>2</PartitionID>
            </ModifyPartition>
          </ModifyPartitions>
          <DiskID>0</DiskID>
          <WillWipeDisk>true</WillWipeDisk>
        </Disk>
      </DiskConfiguration>
      <ImageInstall>
      <OSImage>
      <InstallTo>
      <DiskID>0</DiskID>
      <PartitionID>2</PartitionID>
      </InstallTo>
      <InstallToAvailablePartition>false</InstallToAvailablePartition>
      </OSImage>
      </ImageInstall>
      <UserData>
        <AcceptEula>true</AcceptEula>
        <FullName>Testuser</FullName>
        <Organization></Organization>
      </UserData>
      <EnableFirewall>true</EnableFirewall>
    </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>0c09:00000409</InputLocale>
      <SystemLocale>en-AU</SystemLocale>
      <UILanguage>en-AU</UILanguage>
      <UILanguageFallback>en-AU</UILanguageFallback>
      <UserLocale>en-AU</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>Test5-PC</ComputerName>
      <ProductKey>W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9</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>Testuser</Username>
      </AutoLogon>
      <OOBE>
        <HideEULAPage>true</HideEULAPage>
        <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
        <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
        <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
        <NetworkLocation>Work</NetworkLocation>
        <ProtectYourPC>1</ProtectYourPC>
        <SkipUserOOBE>true</SkipUserOOBE>
        <SkipMachineOOBE>true</SkipMachineOOBE>
      </OOBE>
      <UserAccounts>
        <LocalAccounts>
          <LocalAccount wcm:action="add">
            <Password>
              <Value></Value>
              <PlainText>true</PlainText>
            </Password>
            <Description></Description>
            <DisplayName>Testuser</DisplayName>
            <Group>Administrators</Group>
            <Name>Testuser</Name>
          </LocalAccount>
        </LocalAccounts>
      </UserAccounts>
      <RegisteredOrganization></RegisteredOrganization>
      <RegisteredOwner>Testuser</RegisteredOwner>
      <DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet>
      <TimeZone>AUS Eastern Standard Time</TimeZone>
      <VisualEffects>
        <SystemDefaultBackgroundColor>2</SystemDefaultBackgroundColor>
      </VisualEffects>
    </component>
    <component name="Microsoft-Windows-ehome-reg-inf" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="NonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <RestartEnabled>true</RestartEnabled>
    </component>
    <component name="Microsoft-Windows-ehome-reg-inf" 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">
      <RestartEnabled>true</RestartEnabled>
    </component>
  </settings>
</unattend>

PowerShell script:

$ComputerName = "Test2-PC"
#Set ComputerName in unattend file
$filePath = "C:\unattend.xml"
[xml]$xml = Get-Content $filePath.TrimStart('"').TrimEnd('"')
$node = $xml.unattend | foreach settings | foreach component | foreach computername
$content = Get-Content -Path $filePath.TrimStart('"').TrimEnd('"')
$content | foreach {
    $_.Replace($node.Trim(), $ComputerName)
} | Set-Content $filePath.TrimStart('"').TrimEnd('"')

The error is:

You cannot call a method on a null-valued expression.
At line:1 char:1
+ $node.Trim()
+ ~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

Typing $node shows the value as Test5-PC so it is assigned a value.

Running:

if ($node -eq $null) {
    Write-Host "is null"
} else {
    Write-Host "is not null"
}

Results in "is null", so it's null but at the same time it has a value?

Removing the .Trim() from $node.Trim() makes the script run without errors, but the XML value isn't updated in the XML file.

What am I missing here?

Upvotes: 1

Views: 1668

Answers (3)

Ansgar Wiechers
Ansgar Wiechers

Reputation: 200453

You have 11 <settings> nodes in your XML file, but only one of them has a nested node <ComputerName>. Since your code expands all <settings> nodes, and then tries to expand nested <component> and <ComputerName> nodes you end up with an array with 11 elements, 10 of which are $null. You can verify that by piping $node into an output loop like this:

PS C:\> $node | ForEach-Object { "-$_-" }
--
--
--
--
--
--
--
-Test5-PC-
--
--
--

Instead of messing around with string manipulations make proper use of the XML parser that's built into PowerShell. Use the SelectSingleNode() method with an XPath expression to select the node you want to modify, change its value, then save the file.

$ComputerName = "Test2-PC"
$filePath = "C:\unattend.xml"

[xml]$xml = Get-Content $filePath

$nsm = New-Object Xml.XmlNamespaceManager($xml.NameTable)
$nsm.AddNamespace("ns", $xml.DocumentElement.NamespaceURI)
$node = $xml.SelectSingleNode("//ns:ComputerName", $nsm)
$node.'#text' = $ComputerName

$xml.Save($filePath)

Note that you need a namespace manager here, because your XML file uses namespaces (xmlns=...). Note also, that XPath expressions are case-sensitive, so //ns:computername will NOT work. You MUST use //ns:ComputerName.

Upvotes: 4

Spagett
Spagett

Reputation: 147

Solved by specifying $node as a string:

$ComputerName = "Test2-PC"
#Set ComputerName in unattend file
$filePath = "C:\unattend.xml"
[xml]$xml = Get-Content $filePath.TrimStart('"').TrimEnd('"')
[string]$node = $xml.unattend | foreach settings | foreach component | foreach computername
$content = Get-Content -Path $filePath.TrimStart('"').TrimEnd('"')
$content | foreach {
    $_.Replace($node.Trim(), $ComputerName)
} | Set-Content $filePath.TrimStart('"').TrimEnd('"')

Upvotes: 0

Mark Wragg
Mark Wragg

Reputation: 23395

You could do this with a RegEx-based -Replace:

$ComputerName = "Test2-PC"
$filePath = ".\unattend.xml"

(Get-Content $filePath) -Replace "^<ComputerName>(.*?)</ComputerName>$","<ComputerName>$ComputerName</ComputerName>" | Set-Content $filePath

Upvotes: 0

Related Questions