Convert vbscript to powershell used in WinPE

Now that we have powershell V3 in WinPE4, the last vbscript I’ve been using can be converted.
I don’t take credits for writing this vbscript, I’ve downloaded it from someone who published it as ‘untoy.vbs’ on his blog that disappeared years ago.
I was using it to inject computernames and other preferences in either the unattend.xml used by setup.exe to install Vista+ computers or the unattend.xml used to boot syspreped images, located in C:\Windows\Panther.

Here is the vbscript

	Dim args
	Dim numargs
	Dim config
	Dim source_unattend
	Dim target_unattend

	Dim OldXMLNode
	Dim NewXMLNode
	Dim parent

	Set args = WScript.Arguments
	numargs = args.Count
		
	'check if user supplied 3 arguments
	If(numargs < 3) Then
            WScript.Echo "Usage: [CScript | WScript] untoy.vbs config source_unattend target_unattend"
            WScript.Quit 1
        End If
		
		config = args.Item(0)
		source_unattend = args.Item(1)
		target_unattend = args.Item(2)
		
        'load up source unattend file
        Set SourceXMLDoc = CreateObject("MSXML2.DOMDocument")
        SourceXMLDoc.Async = False
	SourceXMLDoc.load source_unattend
        If SourceXMLDoc.parseError.errorCode <> 0 Then
	        WScript.Echo "Error parsing source unattend file"
	        WScript.Quit 1
        End If
        
        'load up config file
        Set objFSO = CreateObject("Scripting.FileSystemObject")
        Set objTextFile = objFSO.OpenTextFile(config, 1)
        Dim ConfigArray()
        intSize = -1
        
        Do While objTextFile.AtEndOfStream <> True
            str = objtextFile.Readline
            If inStr(str, ",") Then
                tempArr = split(str, ",")
                intSize = intSize + 2
                ReDim Preserve ConfigArray(intSize)
                ConfigArray(intSize - 1) = tempArr(0)
                ConfigArray(intSize) = tempArr(1)
            Else
                objTextFile.Skipline
            End If
        Loop
 
        'check if config file was empty
        If(intSize = -1) Then
            WScript.Echo "Nothing in config file."
            WScript.Quit 1
        End If

        'apply config file changes to source XML
        SourceXMLDoc.setProperty "SelectionNamespaces", "xmlns:un='urn:schemas-microsoft-com:unattend'"
        SourceXMLDoc.setProperty "SelectionLanguage", "XPath"
        
        Set OldXMLNode = SourceXMLDoc.documentElement.SelectSingleNode(ConfigArray(i))

        i = 0
        Do While i <= intSize
            Set OldXMLNode = SourceXMLDoc.documentElement.SelectSingleNode(ConfigArray(i))
	    if OldXMLNode is Nothing Then
	     wscript.echo "No match for " & ConfigArray(i)
 	    else
 	        Set ParentNode = OldXMLNode.parentNode
	       	Set NewXMLnode = OldXMLNode
           	NewXMLNode.text = ConfigArray(i+1)           
            	ParentNode.replaceChild NewXMLNode, OldXMLNode
	    end if
            i = i + 2
        Loop

        'save modified source XML to target file
        SourceXMLDoc.Save target_unattend

Here’s a sample XML, I’ve highlithed what has to be changed by the above vbscript


<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="windowsPE">
    </settings>
    <settings pass="offlineServicing">
    </settings>
    <settings pass="specialize">
        <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">
            <OEMInformation>
                <HelpCustomized>false</HelpCustomized>
            </OEMInformation>
            <BluetoothTaskbarIconEnabled>false</BluetoothTaskbarIconEnabled>
            <ShowWindowsLive>false</ShowWindowsLive>
            <TimeZone>Romance Standard Time</TimeZone>
            <ComputerName>ChangeThisComputerName</ComputerName>
        </component>
        <component name="Microsoft-Windows-UnattendedJoin" 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">
            <Identification>
		<Credentials>
			<Domain>DomainName</Domain>
			<Username>UserName</Username>
			<Password>Password</Password>
		</Credentials>
                <JoinDomain>Domain Name to Join</JoinDomain>
            </Identification>
        </component>
    </settings>
    <settings pass="oobeSystem">
    </settings>
    <cpi:offlineImage cpi:source="catalog:d:/distrib/x64/os/sources/install_windows 7 enterprise.clg" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
</unattend>

Inside my custom WinPE image, I used it like this:
(where %_TARGETARCH% is either amd64 or x86, _PCNAME is the target computername,…)


:: Dump config to a file
echo //un:settings[@pass='specialize']//un:component[@name='Microsoft-Windows-Shell-Setup' and @processorArchitecture='%_TARGETARCH%']/un:ComputerName,%_PCNAME%>C:\$OEM$\$$\config.txt
echo //un:settings[@pass='specialize']//un:component[@name='Microsoft-Windows-UnattendedJoin' and @processorArchitecture='%_TARGETARCH%']/un:Identification/un:Credentials/un:Password,%_INSTPW%>>C:\$OEM$\$$\config.txt
echo //un:settings[@pass='specialize']//un:component[@name='Microsoft-Windows-UnattendedJoin' and @processorArchitecture='%_TARGETARCH%']/un:Identification/un:Credentials/un:Domain,%_WINSDOM%>>C:\$OEM$\$$\config.txt
echo //un:settings[@pass='specialize']//un:component[@name='Microsoft-Windows-UnattendedJoin' and @processorArchitecture='%_TARGETARCH%']/un:Identification/un:Credentials/un:Username,%_ACCOUNTNAME%>>C:\$OEM$\$$\config.txt

:: Create the final xml from our template and config file
cscript /e:vbscript %systemroot%\system32\untoy.txt C:\$OEM$\$$\config.txt %systemroot%\system32\unattend.xml C:\unattend.xml
  • Method1: Using COM objects
$oldxml = New-Object -ComObject MSXML2.DOMDocument            
            
# Here, I'll define an array for testing but it could also be populated by a Get-Content C:\$OEM$\$$\config.txt...            
$config = @()            
$config += "//un:settings[@pass='specialize']//un:component[@name='Microsoft-Windows-Shell-Setup' and @processorArchitecture='amd64']/un:ComputerName,test"            
            
# Loading will return a boolean            
if ($oldxml.load("$env:systemroot\system32\unattend.xml"))            
{            
            
    # Split each line of the array                
    $newconfig = @()            
    $config | ForEach-Object -Process {            
        $newconfig += @{ XPath = ($_ -split ",")[0] ; Value = ($_ -split ",")[1] }            
    }            
    # Apply changes            
    $oldxml.setProperty("SelectionNamespaces", "xmlns:un='urn:schemas-microsoft-com:unattend'")            
    $oldxml.setProperty("SelectionLanguage", "XPath")            
    $newconfig | Foreach-Object -Process {            
        $OldXMLNode = $oldxml.documentElement.SelectSingleNode($_.XPath)            
        $ParentNode = $OldXMLNode.parentNode            
        $NewXMLnode = $OldXMLNode            
        $NewXMLNode.text = $_.Value            
        $ParentNode.replaceChild($NewXMLNode,$OldXMLNode)            
    }            
    # Save changes to the target file            
    $oldxml.save('C:\unattend.xml')            
            
}            
  • Method2: Using .NET objects without Select-XML
# Here, I'll define an array for testing but it could also be populated by a Get-Content C:\$OEM$\$$\config.txt...            
$config = @()            
$config += "//un:settings[@pass='specialize']//un:component[@name='Microsoft-Windows-Shell-Setup' and @processorArchitecture='amd64']/un:ComputerName,test"            
            
# Create .Net object            
$oldxml = New-Object System.Xml.XmlDocument             
            
# Loading doesn't return a boolean so catch exceptions            
try {            
    $oldxml.Load("$env:systemroot\system32\unattend.xml")            
    $loaded = $true            
} catch {            
    $loaded = $false            
    Write-Warning -Message 'Failed to load XML file'            
}            
            
# http://stackoverflow.com/questions/4633127/how-to-select-xml-nodes-with-xml-namespaces-from-an-xmldocument            
# You have to declare the  namespace prefix using an XmlNamespaceManager before you can use it in XPath expressions            
$xmlNameTable = new-object System.Xml.NameTable            
$xmlNameSpace = new-object System.Xml.XmlNamespaceManager($xmlNameTable)            
$xmlNameSpace.AddNamespace("un",'urn:schemas-microsoft-com:unattend')            
            
if ($loaded)            
{            
    # Split each line of the array                
    $newconfig = @()            
    $config | ForEach-Object -Process {            
        $newconfig += @{ XPath = ($_ -split ",")[0] ; Value = ($_ -split ",")[1] }            
    }            
            
    # Apply changes            
    $newconfig | Foreach-Object -Process {            
        $OldXMLNode = $oldxml.DocumentElement.SelectSingleNode($_.XPath,$xmlNameSpace)            
        $ParentNode = $OldXMLNode.ParentNode            
        $NewXMLnode = $OldXMLNode            
        $NewXMLnode.'#text' = $_.Value            
        $ParentNode.replaceChild($NewXMLnode,$OldXMLNode)            
    }            
            
    # Save changes to the target file            
    $oldxml.save('C:\unattend.xml')            
}
  • Method3: Using .NET objects with Select-XML
# Here, I'll define an array for testing but it could also be populated by a Get-Content C:\$OEM$\$$\config.txt...            
$config = @()            
$config += "//un:settings[@pass='specialize']//un:component[@name='Microsoft-Windows-Shell-Setup' and @processorArchitecture='amd64']/un:ComputerName,test"            
            
# Create .Net object            
$oldxml = New-Object System.Xml.XmlDocument             
            
# Loading doesn't return a boolean so catch exceptions            
try {            
    $oldxml.Load("$env:systemroot\system32\unattend.xml")            
    $loaded = $true            
} catch {            
    $loaded = $false            
    Write-Warning -Message 'Failed to load XML file'            
}            
            
if ($loaded)            
{            
    # Split each line of the array                
    $newconfig = @()            
    $config | ForEach-Object -Process {            
        $newconfig += @{ XPath = ($_ -split ",")[0] ; Value = ($_ -split ",")[1] }            
    }            
            
    # Define a namespace as a hashtable            
    $ns = @{ un = 'urn:schemas-microsoft-com:unattend'}            
            
    $newconfig | ForEach-Object -Process {            
            
        $OldXMLNode =  $oldxml | Select-Xml -XPath $_.XPath -Namespace $ns            
        $NewXMLnode = $OldXMLNode            
        $NewXMLnode.Node.'#text' = $_.Value            
        $ParentNode = $OldXMLNode.Node.ParentNode            
        $ParentNode.ReplaceChild($NewXMLnode.Node,$OldXMLNode.Node)            
    }            
            
    # Save changes to the target file            
    $oldxml.save('C:\unattend.xml')            
            
}            
            
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s