UnattendXmlBuilder.psm1

using namespace System
using namespace System.Collections
using namespace System.IO
using namespace System.Management.Automation
using namespace System.Text
using namespace System.Xml
enum RdpSecurityLayer
{
    RDP       = 0
    Negotiate = 1
    TLS       = 2
}
enum TpmClearBehavior
{
    Never     = 0
    WhenOwner = 1
    Always    = 2
}
class UnattendBuilder
{
#region Properties
    hidden [xml] $UnattendXml
    hidden [XmlNamespaceManager] $NamespaceManager
    hidden [XmlElement[]] $Passes = [XmlElement[]]::new(7)
    hidden [string] $Namespace = 'builder'
#endregion

#region Constructors
    UnattendBuilder()
    {
        $Xml  = [xml]::new()
        $null = $Xml.AppendChild($Xml.CreateXmlDeclaration("1.0", 'utf-8', $null))
        $null = $Xml.AppendChild($Xml.CreateElement('unattend', "urn:schemas-microsoft-com:unattend"))
        $null = $Xml.ChildNodes[1].SetAttribute('xmlns', 'http://www.w3.org/2000/xmlns/', 'urn:schemas-microsoft-com:unattend')

        $this.UnattendXml = $Xml
        $this.SetNamespaceManager()
    }

    UnattendBuilder([xml] $LoadedXml)
    {
        $this.UnattendXml = $LoadedXml
        $this.SetNamespaceManager()
        $this.UpdatePasses()
    }

    UnattendBuilder([string] $XmlFilePath)
    {
        $Xml = [xml]::new()
        $Xml.Load($XmlFilePath)
        $this.UnattendXml = $Xml
        $this.SetNamespaceManager()
        $this.UpdatePasses()
    }
#endregion

#region Internal methods
    hidden [XmlElement] AddPass([UnattendPass] $Pass)
    {
        $NewPass = $this.UnattendXml.CreateElement("settings", 'urn:schemas-microsoft-com:unattend')
        $NewPass.SetAttribute("pass", $Pass)
        $this.Passes[$Pass.value__] = $NewPass
        return $NewPass
    }

    hidden [void] SetNamespaceManager()
    {
        $Manager = [XmlNamespaceManager]::new($this.UnattendXml.NameTable)
        $Manager.AddNamespace($this.Namespace, 'urn:schemas-microsoft-com:unattend')
        $this.NamespaceManager = $Manager
    }

    hidden [void] UpdatePasses()
    {
        foreach ($Pass in [Enum]::GetValues([UnattendPass]))
        {
            $FoundItem = $this.UnattendXml.SelectSingleNode("./$($this.Namespace):unattend/$($this.Namespace):settings[@pass = '$Pass']", $this.NamespaceManager)
            if ($null -ne $FoundItem)
            {
                $this.Passes[$Pass.value__] = $FoundItem
            }
        }
    }

    hidden [XmlElement] CreateComponent([string] $ComponentName)
    {
        return $this.CreateElement(
            "component",
            [ordered]@{
                name                  = $ComponentName
                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'
            },
            $true
        )
    }
#endregion

#region Public methods
    [void] AddSimpleListToElement([string[]] $List, [string] $ItemName, [XmlElement] $Element)
    {
        for ($i = 0; $i -lt $List.Count; $i++)
        {
            $this.CreateAndAppendElement(
                $ItemName,
                $List[$i],
                [ordered]@{
                    action   = 'add'
                    keyValue = "$i"
                },
                $Element
            )
        }
    }

    [void] AddHashtableValuesToElement([hashtable] $Table, [XmlElement] $Element)
    {
        foreach ($Key in $Table.Keys)
        {
            $Value = $Table[$Key]

            if ($Value -is [bool])
            {
                $null = $Element.AppendChild($this.CreateElement($Key, $Value))
            }
            else
            {
                $null = $Element.AppendChild($this.CreateElement($Key, $Value))
            }
        }
    }

    [void] AddCredentialToElement([pscredential] $Credential, [XmlElement] $Element)
    {
        $CredentialsElement = $Element.AppendChild($this.CreateElement('Credentials'))
        $NetCreds = $Credential.GetNetworkCredential()
        if (![string]::IsNullOrEmpty($NetCreds.Domain))
        {
            $this.CreateAndAppendElement('Domain', $NetCreds.Domain, $CredentialsElement)
        }
        $this.CreateAndAppendElement('Username', $NetCreds.UserName, $CredentialsElement)
        $this.CreateAndAppendElement('Password', $NetCreds.Password, $CredentialsElement)
    }

    [void] SetCredentialOnElement([pscredential] $Credential, [XmlElement] $Element)
    {
        $CredentialsElement = $Element.SelectSingleNode("./$($this.Namespace):Credentials", $this.NamespaceManager)
        if ($null -eq $CredentialsElement)
        {
            $CredentialsElement = $Element.AppendChild($this.CreateElement('Credentials'))
        }
        else
        {
            $CredentialsElement = $Element.ReplaceChild($this.CreateElement('Credentials'), $CredentialsElement)
        }

        $NetCreds = $Credential.GetNetworkCredential()
        if (![string]::IsNullOrEmpty($NetCreds.Domain))
        {
            $this.CreateAndAppendElement('Domain', $NetCreds.Domain, $CredentialsElement)
        }
        $this.CreateAndAppendElement('Username', $NetCreds.UserName, $CredentialsElement)
        $this.CreateAndAppendElement('Password', $NetCreds.Password, $CredentialsElement)
    }

    [XmlElement] GetOrCreatePass([UnattendPass] $Pass)
    {
        $ReturnPass = $this.Passes[$Pass.value__]
        if ($null -eq $ReturnPass)
        {
            $ReturnPass = $this.AddPass($Pass)
        }

        return $ReturnPass
    }

    [XmlElement] GetOrCreateComponent([string] $ComponentName, [UnattendPass] $Pass)
    {
        $PassElement = $this.GetOrCreatePass($Pass)
        $ComponentElement = $PassElement.SelectSingleNode("./$($this.Namespace):component[@name='$ComponentName']", $this.NamespaceManager)
        if ($null -eq $ComponentElement)
        {
            $ComponentElement = $PassElement.AppendChild($this.CreateComponent($ComponentName))
        }

        return $ComponentElement
    }

    [XmlElement] GetOrCreateChildElement([string] $ElementName, [XmlElement] $Parent)
    {
        $ChildElement = $Parent.SelectSingleNode("./$($this.Namespace):$ElementName", $this.NamespaceManager)
        if ($null -eq $ChildElement)
        {
            $ChildElement = $Parent.AppendChild($this.CreateElement($ElementName))
        }

        return $ChildElement
    }

    [XmlElement] GetChildElementFromXpath ([string]$Path, [XmlElement] $Parent)
    {
        # The regex adds the proper namespace to each path segment that needs one.
        # Relative path segments, and special xpath function calls don't get it.
        $RealPath = $Path -replace '\/(?=\w+(?:\/|$))', "/$($this.Namespace):"
        return $Parent.SelectSingleNode($RealPath, $this.NamespaceManager)
    }

    [XmlElement] CreateElement([string] $Name)
    {
        return $this.UnattendXml.CreateElement($Name, 'urn:schemas-microsoft-com:unattend')
    }

    [XmlElement] CreateElement([string] $Name, [IDictionary] $AttributesToAdd)
    {
        return $this.CreateElement($Name, $AttributesToAdd, $false)
    }

    [XmlElement] CreateElement([string] $Name, [IDictionary] $AttributesToAdd, [bool] $NoNamespaceUri)
    {
        $Element = $this.CreateElement($Name)
        foreach ($Key in $AttributesToAdd.Keys)
        {
            if ($NoNamespaceUri)
            {
                $null = $Element.SetAttribute($Key, $AttributesToAdd[$Key])
            }
            else
            {
                $null = $Element.SetAttribute($Key, 'http://schemas.microsoft.com/WMIConfig/2002/State', $AttributesToAdd[$Key])
            }
        }
        return $Element
    }

    [XmlElement] CreateElement([string] $Name, [string] $Value)
    {
        $Element = $this.CreateElement($Name)
        $ElementValue = $this.UnattendXml.CreateTextNode($Value)
        $null = $Element.AppendChild($ElementValue)
        return $Element
    }

    [XmlElement] CreateElement([string] $Name, [string] $Value, [IDictionary] $AttributesToAdd)
    {
        $Element = $this.CreateElement($Name, $AttributesToAdd)
        $ElementValue = $this.UnattendXml.CreateTextNode($Value)
        $null = $Element.AppendChild($ElementValue)
        return $Element
    }

    [XmlElement] CreateElement([string] $Name, [bool] $Value)
    {
        return $this.CreateElement($Name, $Value.ToString().ToLower())
    }

    [void] CreateAndAppendElement([string] $Name, [IDictionary] $AttributesToAdd, [XmlElement] $Parent)
    {
        $null = $Parent.AppendChild($this.CreateElement($Name, $AttributesToAdd))
    }

    [void] CreateAndAppendElement([string] $Name, [string] $Value, [XmlElement] $Parent)
    {
        $null = $Parent.AppendChild($this.CreateElement($Name, $Value))
    }

    [void] CreateAndAppendElement([string] $Name, [string] $Value, [IDictionary] $AttributesToAdd, [XmlElement] $Parent)
    {
        $null = $Parent.AppendChild($this.CreateElement($Name, $Value, $AttributesToAdd))
    }

    [void] CreateAndAppendElement([string] $Name, [bool] $Value, [XmlElement] $Parent)
    {
        $null = $Parent.AppendChild($this.CreateElement($Name, $Value))
    }

    [void] SetElementValue([string] $Name, [string] $Value, [XmlElement] $Parent)
    {
        $Element = $this.GetOrCreateChildElement($Name, $Parent)
        if ($Element.HasChildNodes)
        {
            $null = $Element.ReplaceChild($this.UnattendXml.CreateTextNode($Value), $Element.FirstChild)
        }
        else
        {
            $null = $Element.AppendChild($this.UnattendXml.CreateTextNode($Value))
        }
    }

    [void] SetElementValue([string] $Name, [bool] $Value, [XmlElement] $Parent)
    {
        $this.SetElementValue($Name, $Value.ToString().ToLower(), $Parent)
    }

    [xml] ToXml()
    {
        foreach ($Pass in $this.Passes)
        {
            if ($null -ne $Pass)
            {
                $ExistingNode = $this.UnattendXml.SelectSingleNode("/$($this.Namespace):unattend/$($this.Namespace):settings[@pass=$($Pass.pass)]", $this.NamespaceManager)
                if ($null -eq $ExistingNode)
                {
                    $null = $this.UnattendXml.ChildNodes[1].AppendChild($Pass)
                }
            }
        }

        return $this.UnattendXml
    }

    [string] ToString()
    {
        $Xml = $this.ToXml()
        $Writer = [StringWriter]::new()
        $Xml.Save($Writer)
        $Text = $Writer.ToString()
        $Writer.Dispose()
        return $Text
    }
#endregion
}
enum UnattendPass
{
    windowsPE        = 0
    offlineServicing = 1
    generalize       = 2
    specialize       = 3
    auditSystem      = 4
    auditUser        = 5
    oobeSystem       = 6
}
function AddFirewallGroupsToElement
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter(Mandatory)]
        [XmlElement]
        $Parent,

        [Parameter(Mandatory)]
        [string[]]
        $GroupNames,

        [Parameter(Mandatory)]
        [string]
        $FirewallProfile,

        [Parameter(Mandatory)]
        [bool]
        $Active
    )
    process
    {
        foreach ($GroupName in $GroupNames)
        {
            $Attributes = [ordered]@{
                action   = 'add'
                keyValue = (New-Guid).Guid
            }
            $FwGroupElement = $Parent.AppendChild($UnattendBuilder.CreateElement("FirewallGroup", $Attributes))
            $UnattendBuilder.CreateAndAppendElement("Group", $GroupName, $FwGroupElement)
            $UnattendBuilder.CreateAndAppendElement("Active", $Active, $FwGroupElement)
            $UnattendBuilder.CreateAndAppendElement("Profile", $FirewallProfile, $FwGroupElement)
        }
    }
}
function EncodeUnattendPassword
{
    [OutputType([String])]
    Param
    (
        [Parameter(Mandatory)]
        [string]
        $Password,

        [Parameter()]
        [ValidateSet('OfflineLocalAdmin', 'LocalAdmin', 'UserAccount')]
        [string]
        $Kind
    )
    End
    {
        $MagicString = switch ($Kind)
        {
            'OfflineLocalAdmin' {'OfflineAdministratorPassword';break}
            'LocalAdmin' {'AdministratorPassword';break}
            'UserAccount' {'Password';break}
            Default{''}
        }
        [Convert]::ToBase64String([Encoding]::Unicode.GetBytes($Password + $MagicString))
    }
}
<#
.SYNOPSIS
    Adds commands that are run automatically at logon, or during the installation.
 
.DESCRIPTION
    Adds commands that are run automatically at logon, or during the installation.
    Commands can be set to run at first login, or on every login.
    Multiple commands can be specified at once, but if more specific settings need to be specified (different descriptions) then you can run this command multiple times to add each command.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass where the commands should run.
    Valid values are:
    windowsPE
    specialize (default)
    auditUser
 
.PARAMETER FirstLogonCommand
    Specifies that the command should only run the first time a user logs in.
 
.PARAMETER LogonCommand
    Specifies that the command is persistent, and should run every time a user logs in.
 
.PARAMETER Async
    Specifies that the command is run asynchronously so the logon process can finish quicker.
 
.PARAMETER RequiresUserInput
    Specifies that the command requires user input.
 
.PARAMETER RebootBehavior
    Controls how rebooting should be handled.
    Valid values are:
    Never - Never reboot the system.
    Always - Always reboot the system after this command.
    OnRequest - Reboot if the command returns with specific exit codes (1, 2)
 
.PARAMETER Command
    Specifies the command line to run.
 
.PARAMETER Description
    Specifies a description for the command.
 
.PARAMETER RunAsCredential
    Specifies alternative credentials that the command should run as.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Add-UnattendCommand
{
    [CmdletBinding(DefaultParameterSetName = 'Default', PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter(ParameterSetName = 'Default')]
        [ValidateSet('windowsPE', 'specialize', 'auditUser')]
        [string]
        $Pass = 'specialize',

        [Parameter(Mandatory, ParameterSetName = 'FirstLogon')]
        [switch]
        $FirstLogonCommand,

        [Parameter(Mandatory, ParameterSetName = 'LogonPersistent')]
        [switch]
        $LogonCommand,

        [Parameter(ParameterSetName = 'Default')]
        [switch]
        $Async,

        [Parameter(ParameterSetName = 'FirstLogon')]
        [Parameter(ParameterSetName = 'LogonPersistent')]
        [switch]
        $RequiresUserInput,

        [Parameter(ParameterSetName = 'Default')]
        [ValidateSet('Never', 'Always', 'OnRequest')]
        [string]
        $RebootBehavior,

        [Parameter(Mandatory, Position = 0)]
        [string[]]
        $Command,

        [Parameter()]
        [string]
        $Description,

        [Parameter(ParameterSetName = "Default")]
        [pscredential]
        $RunAsCredential
    )
    process
    {
        if ($FirstLogonCommand)
        {
            $Component         = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', 'oobeSystem')
            $ParentElementName = 'FirstLogonCommands'
            $ElementName       = 'SynchronousCommand'
            $CmdElementName    = 'CommandLine'
        }
        elseif ($LogonCommand)
        {
            $Component         = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', 'oobeSystem')
            $ParentElementName = 'LogonCommands'
            $ElementName       = 'AsynchronousCommand'
            $CmdElementName    = 'CommandLine'
        }
        else
        {
            $CmdElementName = 'Path'
            if ($Pass -eq "windowsPE")
            {
                $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Setup', 'WindowsPE')
            }
            else
            {
                $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Deployment', $Pass)
            }

            if ($Async)
            {
                $ParentElementName = 'RunAsynchronous'
                $ElementName = 'RunAsynchronousCommand'
            }
            else
            {
                $ParentElementName = 'RunSynchronous'
                $ElementName = 'RunSynchronousCommand'
            }
        }

        $ParentElement = $UnattendBuilder.GetOrCreateChildElement($ParentElementName, $Component)
        $CommandCounter = $ParentElement.ChildNodes.Count + 1

        foreach ($Cmd in $Command)
        {
            $CommandElement = $ParentElement.AppendChild($UnattendBuilder.CreateElement($ElementName, @{action = 'add'}))
            $UnattendBuilder.CreateAndAppendElement($CmdElementName, $Cmd, $CommandElement)
            $UnattendBuilder.CreateAndAppendElement('Order', ($CommandCounter++), $CommandElement)

            switch ($PSBoundParameters.Keys)
            {
                'RequiresUserInput'
                {
                    $UnattendBuilder.CreateAndAppendElement('RequiresUserInput', $RequiresUserInput, $CommandElement)
                    continue
                }
                'RebootBehavior'
                {
                    if ($Async)
                    {
                        Write-Warning "$_ cannot be set for async commands. Skipping this property."
                    }
                    elseif ($Pass -eq "windowsPE")
                    {
                        Write-Warning "$_ cannot be set for WinPE commands. Skipping this property."
                    }
                    else
                    {
                        $UnattendBuilder.CreateAndAppendElement('WillReboot', $RebootBehavior, $CommandElement)
                    }
                    continue
                }
                'Description'
                {
                    $UnattendBuilder.CreateAndAppendElement('Description', $Description, $CommandElement)
                    continue
                }
                'RunAsCredential'
                {
                    $UnattendBuilder.AddCredentialToElement($RunAsCredential, $CommandElement)
                    continue
                }
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Adds disk partitioning related settings to the unattend file.
 
.DESCRIPTION
    Adds disk partitioning related settings to the unattend file.
    You can either use one of the predefined templates that handle all the partitioning, or run this command multiple times to add all the custom partitions you need.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Template
    Specifies the template to use.
    BIOS will create 3 partitions (System (100MB), Recovery (620MB), Windows (Rest of disk))
    UEFI will create 4 partitions (System (100MB), MSR (16MB), Recovery (620MB), Windows (Rest of disk))
 
.PARAMETER DontWipeDisk
    Specifies that the disk should not be wiped.
 
.PARAMETER DiskNumber
    Specifies which disk to target.
 
.PARAMETER SizeMB
    Specifies how big the partition should be.
 
.PARAMETER UseRemainingSpace
    Specifies that it should use the remaining space on the disk for this partition.
 
.PARAMETER PartitionType
    Specifies what kind of partition should be created.
 
.PARAMETER Active
    Specifies that the partition should be marked "Active" (this is needed for the System partition on BIOS layouts)
 
.PARAMETER Filesystem
    Specifies the filesystem for this partition.
 
.PARAMETER VolumeLabel
    Specifies a custom label for this partition.
 
.PARAMETER DriveLetter
    Assigns a driveletter to this partition.
 
.PARAMETER PartitionTypeID
    Specifies a custom partition ID to be set. This is rarely needed.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Add-UnattendDiskPartition
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter(Mandatory, ParameterSetName = "Predefined")]
        [ValidateSet("BIOS", "UEFI")]
        [string]
        $Template,

        [Parameter()]
        [switch]
        $DontWipeDisk,

        [Parameter(Mandatory)]
        [uint32]
        $DiskNumber,

        [Parameter(Mandatory, ParameterSetName = "CustomSize")]
        [uint32]
        $SizeMB,

        [Parameter(Mandatory, ParameterSetName = "CustomExtend")]
        [switch]
        $UseRemainingSpace,

        [Parameter(ParameterSetName = "CustomSize")]
        [Parameter(ParameterSetName = "CustomExtend")]
        [ValidateSet('Primary', 'EFI', 'MSR', 'Recovery', 'RecoveryBIOS')]
        [string]
        $PartitionType = 'Primary',

        [Parameter(ParameterSetName = "CustomSize")]
        [Parameter(ParameterSetName = "CustomExtend")]
        [switch]
        $Active,

        [Parameter(ParameterSetName = "CustomSize")]
        [Parameter(ParameterSetName = "CustomExtend")]
        [ValidateSet('FAT32', 'NTFS')]
        [string]
        $Filesystem,

        [Parameter(ParameterSetName = "CustomSize")]
        [Parameter(ParameterSetName = "CustomExtend")]
        [string]
        $VolumeLabel,

        [Parameter(ParameterSetName = "CustomSize")]
        [Parameter(ParameterSetName = "CustomExtend")]
        [char]
        $DriveLetter,

        [Parameter(ParameterSetName = "CustomSize")]
        [Parameter(ParameterSetName = "CustomExtend")]
        [string]
        $PartitionTypeID
    )
    process
    {
        if ($Template)
        {
            $Disk = @{DiskNumber = $DiskNumber}
            $SystemCommonParams = @{
                DontWipeDisk = $DontWipeDisk
                SizeMB       = 100
                VolumeLabel  = "System"
            }
            $RecoveryParams = @{
                SizeMB        = 620
                Filesystem    = "NTFS"
                VolumeLabel   = "Recovery"
            }
            $WindowsParams = @{
                UseRemainingSpace = $true
                FileSystem        = "NTFS"
                PartitionType     = "Primary"
                VolumeLabel       = "Windows"
                DriveLetter       = "C"
            }
            if ($Template -eq 'BIOS')
            {
                $UnattendBuilder |
                    Add-UnattendDiskPartition @Disk @SystemCommonParams -PartitionType Primary -Active -Filesystem NTFS |
                    Add-UnattendDiskPartition @Disk @RecoveryParams -PartitionType RecoveryBIOS |
                    Add-UnattendDiskPartition @Disk @WindowsParams
            }
            else
            {
                $UnattendBuilder |
                    Add-UnattendDiskPartition @Disk @SystemCommonParams -PartitionType EFI -Filesystem FAT32 |
                    Add-UnattendDiskPartition @Disk -SizeMB 16 -PartitionType MSR |
                    Add-UnattendDiskPartition @Disk @RecoveryParams -PartitionType Recovery |
                    Add-UnattendDiskPartition @Disk @WindowsParams
            }
        }
        else
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Setup', 'windowsPE')
            $DiskConfig = $UnattendBuilder.GetOrCreateChildElement('DiskConfiguration', $Component)
            $DiskElement = $UnattendBuilder.GetChildElementFromXpath("./Disk/DiskID/text()[. = '$DiskNumber']/../..", $DiskConfig)
            $AddAction = @{action = 'add'}

            if ($null -eq $DiskElement)
            {
                $DiskElement = $DiskConfig.AppendChild($UnattendBuilder.CreateElement("Disk", $AddAction))
                $UnattendBuilder.CreateAndAppendElement("DiskID", $DiskNumber, $DiskElement)
            }

            if ($PSBoundParameters.ContainsKey('DontWipeDisk'))
            {
                $UnattendBuilder.SetElementValue('WillWipeDisk', !$DontWipeDisk, $DiskElement)
            }

            $CreatePartitionsElement = $UnattendBuilder.GetOrCreateChildElement('CreatePartitions', $DiskElement)
            $ModifyPartitionsElement = $UnattendBuilder.GetOrCreateChildElement('ModifyPartitions', $DiskElement)
            $Order = $CreatePartitionsElement.ChildNodes.Count + 1

            $Partition = $CreatePartitionsElement.AppendChild($UnattendBuilder.CreateElement("CreatePartition", $AddAction))
            $UnattendBuilder.CreateAndAppendElement("Order", $Order, $Partition)
            if ($SizeMB)
            {
                $UnattendBuilder.CreateAndAppendElement("Size", $SizeMB, $Partition)
            }
            else
            {
                $UnattendBuilder.CreateAndAppendElement('Extend', $UseRemainingSpace, $Partition)
            }

            if ($PartitionType -eq "Recovery")
            {
                $RealPartitionType = "Primary"
                $RealCustomPartitionID = 'DE94BBA4-06D1-4D40-A16A-BFD50179D6AC'
            }
            elseif ($PartitionType -eq "RecoveryBIOS")
            {
                $RealPartitionType = "Primary"
                $RealCustomPartitionID = '0x27'
            }
            else
            {
                $RealPartitionType = $PartitionType
                $RealCustomPartitionID = $PartitionTypeID
            }
            $UnattendBuilder.CreateAndAppendElement('Type', $RealPartitionType, $Partition)

            $ModifyPartition = $ModifyPartitionsElement.AppendChild($UnattendBuilder.CreateElement("ModifyPartition", $AddAction))
            $UnattendBuilder.CreateAndAppendElement("Order", $Order, $ModifyPartition)
            $UnattendBuilder.CreateAndAppendElement("PartitionID", $Order, $ModifyPartition)

            if ($VolumeLabel)
            {
                $UnattendBuilder.CreateAndAppendElement("Label", $VolumeLabel, $ModifyPartition)
            }
            if ($Filesystem)
            {
                $UnattendBuilder.CreateAndAppendElement("Format", $Filesystem, $ModifyPartition)
            }
            if ($RealCustomPartitionID)
            {
                $UnattendBuilder.CreateAndAppendElement("TypeID", $RealCustomPartitionID, $ModifyPartition)
            }
            if ($PSBoundParameters.ContainsKey('Active'))
            {
                $UnattendBuilder.CreateAndAppendElement("Active", $Active, $ModifyPartition)
            }

            $UnattendBuilder
        }
    }
}
<#
.SYNOPSIS
    Configures the paths Windows should use to look for drivers to install during setup.
 
.DESCRIPTION
    Configures the paths Windows should use to look for drivers to install during setup.
    Any driver files found will be added to the driverstore.
    You can run this command multiple times if you need to specify multiple UNC paths with different credentials.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    The pass that this command should apply to.
    Supported values:
    windowsPE
    offlineServicing (default)
    auditSystem
 
.PARAMETER Path
    The folder that contains the drivers to install.
    This folder will be checked recursively for drivers that can be installed.
    It can either be local, or a UNC path.
 
.PARAMETER Credential
    The credential used to access the specified folder, useful if the specified folder is a UNC path.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Add-UnattendDriverPath
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet('windowsPE', 'offlineServicing', 'auditSystem')]
        [string[]]
        $Pass = 'offlineServicing',

        [Parameter(Position = 0)]
        [string[]]
        $Path,

        [Parameter()]
        [pscredential]
        $Credential
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            $Component = if ($PassName -eq 'windowsPE')
            {
                $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-PnpCustomizationsWinPE', $PassName)
            }
            else
            {
                $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-PnpCustomizationsNonWinPE', $PassName)
            }

            $DriverPaths = $UnattendBuilder.GetOrCreateChildElement("DriverPaths", $Component)
            $Counter = $DriverPaths.ChildNodes.Count + 1
            foreach ($Item in $Path)
            {
                $PathElement = $DriverPaths.AppendChild($UnattendBuilder.CreateElement("PathAndCredentials", @{action = "add";keyValue = ($Counter++)}))
                $UnattendBuilder.CreateAndAppendElement("Path", $Item, $PathElement)
                if ($Credential)
                {
                    $UnattendBuilder.AddCredentialToElement($Credential, $PathElement)
                }
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Adds domain accounts to one or more groups.
 
.DESCRIPTION
    Adds domain accounts to one or more groups.
    Local accounts can be added to groups while creating them with "Add-UnattendAccount".
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass where the account should be added.
    Valid values are:
    offlineServicing
    auditSystem
    oobeSystem (default)
 
.PARAMETER DomainName
    Specifies the name of the domain where the domain account is located.
 
.PARAMETER Name
    Specifies the domain user or group name to add to a group.
 
.PARAMETER SID
    Specifies the SID of the domain user or group to add to a group.
 
.PARAMETER Group
    Specifies the groups the domain account should be added to.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Add-UnattendGroupMember
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter(ParameterSetName = 'DomainAccount')]
        [ValidateSet('auditSystem', 'oobeSystem')]
        [string]
        $Pass = 'oobeSystem',

        [Parameter(Mandatory, ParameterSetName = 'DomainAccount')]
        [string]
        $DomainName,

        [Parameter(Mandatory, ParameterSetName = 'DomainAccount')]
        [string[]]
        $Name,

        [Parameter(Mandatory, ParameterSetName = 'OfflineDomainAccount')]
        [string[]]
        $SID,

        [Parameter(Mandatory)]
        [string[]]
        $Group
    )
    process
    {
        if ($PSCmdlet.ParameterSetName -eq 'OfflineDomainAccount')
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', 'offlineServicing')
            $UserAccountsElement = $UnattendBuilder.GetOrCreateChildElement('OfflineUserAccounts', $Component)
            $DomainAccountsElement = $UnattendBuilder.GetOrCreateChildElement('OfflineDomainAccounts', $UserAccountsElement)
            foreach ($User in $SID)
            {
                $NewAccount = $DomainAccountsElement.AppendChild($UnattendBuilder.CreateElement('OfflineDomainAccount', @{action = 'add'}))
                $UnattendBuilder.CreateAndAppendElement('SID', $User, $NewAccount)
                $UnattendBuilder.CreateAndAppendElement('Group', $Group -join ';', $NewAccount)
            }

        }
        else
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', $Pass)
            $UserAccountsElement = $UnattendBuilder.GetOrCreateChildElement('UserAccounts', $Component)
            $DomainAccountsElement = $UnattendBuilder.GetOrCreateChildElement('DomainAccounts', $UserAccountsElement)
            $DomainElement = $UnattendBuilder.GetChildElementFromXpath("./DomainAccountList/Domain/text()[. = '$DomainName']/../..", $DomainAccountsElement)
            if ($null -eq $DomainElement)
            {
                $DomainElement = $DomainAccountsElement.AppendChild($UnattendBuilder.CreateElement("DomainAccountList", @{action = 'add'}))
                $UnattendBuilder.CreateAndAppendElement("Domain", $DomainName, $DomainElement)
            }
            $NewAccount = $DomainElement.AppendChild($UnattendBuilder.CreateElement('DomainAccount', @{action = 'add'}))
            $UnattendBuilder.CreateAndAppendElement('Name', $Name, $NewAccount)
            $UnattendBuilder.CreateAndAppendElement('Group', $Group -join ';', $NewAccount)
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Adds image source/destination details to the unattend file.
 
.DESCRIPTION
    This command adds image source and destination details to the unattend file.
    This can be used to automate the selection of an OS installation image, as well as one or more data images.
    When selecting a source image, you can use the image index, name or description.
    If you specify multiple sources, the XML file will be invalid.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER SourceImagePath
    The path to the the install/data image file, including the extension (.Wim or .Esd)
 
.PARAMETER SourceImageIndex
    The index of the image to be applied, indexes start at 1.
    This should not be used together with "SourceImageName" nor "SourceImageDescription".
    The index for a particular image. Can be viewed with the command: Get-WindowsImage
 
.PARAMETER SourceImageName
    The name of the image to be applied.
    This should not be used together with "SourceImageIndex" nor "SourceImageDescription".
    The name for a particular image.
 
.PARAMETER SourceImageDescription
    The description of the image to be applied.
    This should not be used together with "SourceImageIndex" nor "SourceImageName".
    The description for a particular image can be viewed with the command: Get-WindowsImage
 
.PARAMETER SourceImageGroup
    The image group on the WDS server that contains the image to be installed.
 
.PARAMETER DestinationDiskID
    The disk number where the image should be applied, typically 0.
 
.PARAMETER DestinationPartitionID
    The ID of the partition where the image should be applied.
 
.PARAMETER Credential
    The credential used to access the image source location (if on a fileshare) or the credential used to log on to the WDS server.
 
.PARAMETER WDS
    Specifies that the image source is WDS (Windows Deployment Services).
 
.PARAMETER DataImage
    Specifies that the image source is a data image.
    Multiple data images can be applied on top of the OS install image to add additional files.
    To apply multiple data images, run this command multiple times.
 
.PARAMETER Compact
    Specifies that the OS image should be compacted when installed to the disk.
    Compacting the OS will make it take up less space, but performance can be slightly decreased.
 
.PARAMETER InstallToAvailablePartition
    When set, the installer will find the first available partition with enough space for the OS, and install it there.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Add-UnattendImage
{
    [CmdletBinding(DefaultParameterSetName = "Standard", PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [string]
        $SourceImagePath,

        [Parameter(ParameterSetName = "Standard")]
        [Parameter(ParameterSetName = "DataImage")]
        [uint32]
        $SourceImageIndex,

        [Parameter()]
        [string]
        $SourceImageName,

        [Parameter(ParameterSetName = "Standard")]
        [Parameter(ParameterSetName = "DataImage")]
        [string]
        $SourceImageDescription,

        [Parameter(ParameterSetName = "WDS")]
        [string]
        $SourceImageGroup,

        [Parameter()]
        [uint32]
        $DestinationDiskID,

        [Parameter()]
        [uint32]
        $DestinationPartitionID,

        [Parameter()]
        [pscredential]
        $Credential,

        [Parameter(Mandatory, ParameterSetName = "WDS")]
        [switch]
        $WDS,

        [Parameter(Mandatory, ParameterSetName = "DataImage")]
        [switch]
        $DataImage,

        [Parameter(ParameterSetName = "Standard")]
        [switch]
        $Compact,

        [Parameter(ParameterSetName = "Standard")]
        [switch]
        $InstallToAvailablePartition
    )
    process
    {
        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Setup', 'windowsPE')

        if ($WDS)
        {
            $SubComponent = $UnattendBuilder.GetOrCreateChildElement('WindowsDeploymentServices', $Component)
            if ($SourceImagePath -or $SourceImageGroup -or $SourceImageName -or $DestinationDiskID -or $DestinationPartitionID)
            {
                $ImageSelection = $SubComponent.AppendChild($UnattendBuilder.CreateElement('ImageSelection'))
                switch ($PSBoundParameters.Keys)
                {
                    'SourceImagePath'
                    {
                        $InstallImage = $UnattendBuilder.GetOrCreateChildElement('InstallImage', $ImageSelection)
                        $UnattendBuilder.CreateAndAppendElement('Filename', $SourceImagePath, $InstallImage)
                        continue
                    }
                    'SourceImageGroup'
                    {
                        $InstallImage = $UnattendBuilder.GetOrCreateChildElement('InstallImage', $ImageSelection)
                        $UnattendBuilder.CreateAndAppendElement('ImageGroup', $SourceImageGroup, $InstallImage)
                        continue
                    }
                    'SourceImageName'
                    {
                        $InstallImage = $UnattendBuilder.GetOrCreateChildElement('InstallImage', $ImageSelection)
                        $UnattendBuilder.CreateAndAppendElement('ImageName', $SourceImageName, $InstallImage)
                        continue
                    }
                    'DestinationDiskID'
                    {
                        $InstallTo = $UnattendBuilder.GetOrCreateChildElement('InstallTo', $ImageSelection)
                        $UnattendBuilder.CreateAndAppendElement('DiskID', $DestinationDiskID, $InstallImage)
                        continue
                    }
                    'DestinationPartitionID'
                    {
                        $InstallTo = $UnattendBuilder.GetOrCreateChildElement('InstallTo', $ImageSelection)
                        $UnattendBuilder.CreateAndAppendElement('PartitionID', $DestinationPartitionID, $InstallImage)
                        continue
                    }
                }
            }
            if ($Credential)
            {
                $Login = $SubComponent.AppendChild($UnattendBuilder.CreateElement('Login'))
                $UnattendBuilder.AddCredentialToElement($Credential, $Login)
            }
        }
        else
        {
            $SubComponent = $UnattendBuilder.GetOrCreateChildElement('ImageInstall', $Component)
            if ($DataImage)
            {
                $ImageElement = $SubComponent.AppendChild($UnattendBuilder.CreateElement("DataImage", @{action = 'add'}))
                $UnattendBuilder.CreateAndAppendElement("Order", $SubComponent.ChildNodes.Count, $ImageElement)
            }
            else
            {
                $ImageElement = $UnattendBuilder.GetOrCreateChildElement('OSImage', $SubComponent)
                if ($PSBoundParameters.ContainsKey('Compact'))
                {
                    $UnattendBuilder.CreateAndAppendElement('Compact', $Compact, $ImageElement)
                }
                if ($PSBoundParameters.ContainsKey('InstallToAvailablePartition'))
                {
                    $UnattendBuilder.CreateAndAppendElement('InstallToAvailablePartition', $InstallToAvailablePartition, $ImageElement)
                }
            }

            if ($SourceImagePath -or $SourceImageIndex -or $SourceImageName -or $SourceImageDescription -or $Credential)
            {
                $InstallFrom = $ImageElement.AppendChild($UnattendBuilder.CreateElement('InstallFrom'))
                switch ($PSBoundParameters.Keys)
                {
                    'SourceImagePath'
                    {
                        $UnattendBuilder.CreateAndAppendElement('Path', $SourceImagePath, $InstallFrom)
                        continue
                    }
                    'SourceImageIndex'
                    {
                        $MetaData = $InstallFrom.AppendChild($UnattendBuilder.CreateElement('MetaData', @{action = 'add'}))
                        $UnattendBuilder.CreateAndAppendElement('Key', '/IMAGE/INDEX', $MetaData)
                        $UnattendBuilder.CreateAndAppendElement('Value', $SourceImageIndex, $MetaData)
                        continue
                    }
                    'SourceImageName'
                    {
                        $MetaData = $InstallFrom.AppendChild($UnattendBuilder.CreateElement('MetaData', @{action = 'add'}))
                        $UnattendBuilder.CreateAndAppendElement('Key', '/IMAGE/NAME', $MetaData)
                        $UnattendBuilder.CreateAndAppendElement('Value', $SourceImageName, $MetaData)
                        continue
                    }
                    'SourceImageDescription'
                    {
                        $MetaData = $InstallFrom.AppendChild($UnattendBuilder.CreateElement('MetaData', @{action = 'add'}))
                        $UnattendBuilder.CreateAndAppendElement('Key', '/IMAGE/DESCRIPTION', $MetaData)
                        $UnattendBuilder.CreateAndAppendElement('Value', $SourceImageDescription, $MetaData)
                        continue
                    }
                    'Credential'
                    {
                        $UnattendBuilder.AddCredentialToElement($Credential, $InstallFrom)
                    }
                }
            }
            if ($DestinationDiskID)
            {
                $InstallTo = $UnattendBuilder.GetOrCreateChildElement('InstallTo', $ImageElement)
                $UnattendBuilder.CreateAndAppendElement('DiskID', $DestinationDiskID, $InstallTo)
            }
            if ($DestinationPartitionID)
            {
                $InstallTo = $UnattendBuilder.GetOrCreateChildElement('InstallTo', $ImageElement)
                $UnattendBuilder.CreateAndAppendElement('PartitionID', $DestinationPartitionID, $InstallTo)
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures DNS settings that are interface specific.
 
.DESCRIPTION
    Configures DNS settings that are interface specific.
    Run this command multiple times with different interface identifiers to add settings for multiple interfaces.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass this command should apply to.
    Valid values are:
    windowsPE
    specialize (Default)
 
.PARAMETER InterfaceIdentifier
    Specifies the interface that these settings should apply to.
    Can either be the friendly name like: "Ethernet" or the Mac address, like: "AA-AA-AA-AA-AA-AA"
 
.PARAMETER InterfaceDomain
    Specifies the DNS domain that should be used for connections out from the specified interface.
    If a global DNS domain has been set then that takes priority, and if nothing is found then the interface domain is used.
 
.PARAMETER EnableDynamicUpdate
    Specifies that A and PTR resource records are registered dynamically.
 
.PARAMETER DisableAdapterDomainRegistration
    Specifies that A and PTR resource records are not registered for this adapter.
 
.PARAMETER DnsServer
    Specifies a list of IP addresses to use when searching for the DNS server on the network.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Add-UnattendInterfaceDnsConfig
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet('windowsPE', 'specialize')]
        [string[]]
        $Pass = 'specialize',

        [Parameter(Mandatory)]
        [string]
        $InterfaceIdentifier,

        [Parameter()]
        [string]
        $InterfaceDomain,

        [Parameter()]
        [switch]
        $EnableDynamicUpdate,

        [Parameter()]
        [switch]
        $DisableAdapterDomainRegistration,

        [Parameter()]
        [ipaddress[]]
        $DnsServer
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-DNS-Client', $PassName)
            $InterfacesElement = $UnattendBuilder.GetOrCreateChildElement("Interfaces", $Component)
            $Interface = $InterfacesElement.AppendChild($UnattendBuilder.CreateElement('Interface', @{action = 'add'}))

            switch ($PSBoundParameters.Keys)
            {
                'EnableDynamicUpdate'
                {
                    $UnattendBuilder.CreateAndAppendElement('DisableDynamicUpdate', !$EnableDynamicUpdate, $Interface)
                    continue
                }
                'InterfaceDomain'
                {
                    $UnattendBuilder.CreateAndAppendElement('DNSDomain', $InterfaceDomain, $Interface)
                    continue
                }
                'DisableAdapterDomainRegistration'
                {
                    $UnattendBuilder.CreateAndAppendElement('EnableAdapterDomainNameRegistration', !$DisableAdapterDomainRegistration, $Interface)
                    continue
                }
                'DnsServer'
                {
                    $DnsElement = $Interface.AppendChild($UnattendBuilder.CreateElement('DNSServerSearchOrder'))
                    $UnattendBuilder.AddSimpleListToElement($DnsServer, "IpAddress", $DnsElement)
                }
            }

            $UnattendBuilder.CreateAndAppendElement('Identifier', $InterfaceIdentifier, $Interface)
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures interface specific settings.
 
.DESCRIPTION
    Configures interface specific settings.
    Run this command multiple times with different interface identifiers to configure multiple interfaces.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass to use.
    Valid values are:
    windowsPE
    specialize (default)
 
.PARAMETER InterfaceIdentifier
    Specifies the interface that settings should apply to.
    Can either be the friendly name like: "Ethernet" or the Mac address, like: "AA-AA-AA-AA-AA-AA"
 
.PARAMETER IpAddress
    The ip address to assign to the interface.
    Can be specified with or without the cidr notation.
    If the cidr notation is left out, Windows will use the class based system to guess the right subnet mask.
 
.PARAMETER DefaultGateway
    Specifies the default gateway the interface should use.
 
.PARAMETER Routes
    Specifies the custom routes to add to the interface.
    Use a hashtable with the following keys:
    Metric - a number that sets the priority for the route, the lower the number, the higher the priority.
    NextHopAddress - What the route should point to.
    Prefix - Specifies which destination IP addresses this route should apply to.
    For more information, see: https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-tcpip-interfaces-interface-routes-route
 
.PARAMETER Ipv4InterfaceSettings
    Specifies ipv4 settings for the interface.
    Use a hashtable with the following keys:
    DhcpEnabled - a bool that controls whether or not DHCP is enabled on this interface.
    Metric - a number that sets the priority for the interface, the lower the number, the higher the priority.
    RouterDiscoveryEnabled - Specifies whether the router discovery protocol, which informs hosts of the existence of routers, is enabled.
    For more information, see: https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-tcpip-interfaces-interface-ipv4settings
 
.PARAMETER Ipv6InterfaceSettings
    Specifies ipv6 settings for the interface.
    Use a hashtable with the following keys:
    DhcpEnabled - a bool that controls whether or not DHCP is enabled on this interface.
    Metric - a number that sets the priority for the interface, the lower the number, the higher the priority.
    RouterDiscoveryEnabled - Specifies whether the router discovery protocol, which informs hosts of the existence of routers, is enabled.
    For more information, see: https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-tcpip-interfaces-interface-ipv6settings
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Add-UnattendInterfaceIpConfig
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet('windowsPE', 'specialize')]
        [string[]]
        $Pass = 'specialize',

        [Parameter(Mandatory)]
        [string]
        $InterfaceIdentifier,

        [Parameter()]
        [string[]]
        $IpAddress,

        [Parameter()]
        [ipaddress]
        $DefaultGateway,

        [Parameter()]
        [hashtable[]]
        $Routes,

        [Parameter()]
        [hashtable]
        $Ipv4InterfaceSettings,

        [Parameter()]
        [hashtable]
        $Ipv6InterfaceSettings
    )
    begin
    {
        if ($DefaultGateway)
        {
            $Routes += @{
                NextHopAddress = $DefaultGateway.ToString()
                Metric = 0
                Prefix = "0.0.0.0/0"
            }
        }
    }

    process
    {
        foreach ($PassName in $Pass)
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-TCPIP', $PassName)
            $InterfacesElement = $UnattendBuilder.GetOrCreateChildElement("Interfaces", $Component)
            $Interface = $InterfacesElement.AppendChild($UnattendBuilder.CreateElement("Interface", @{action = 'add'}))

            if ($Ipv4InterfaceSettings)
            {
                $SettingsElement = $Interface.AppendChild($UnattendBuilder.CreateElement('Ipv4Settings'))
                $UnattendBuilder.AddHashtableValuesToElement($Ipv4InterfaceSettings, $SettingsElement)
            }
            if ($Ipv6InterfaceSettings)
            {
                $SettingsElement = $Interface.AppendChild($UnattendBuilder.CreateElement('Ipv6Settings'))
                $UnattendBuilder.AddHashtableValuesToElement($Ipv6InterfaceSettings, $SettingsElement)
            }

            $UnattendBuilder.CreateAndAppendElement('Identifier', $InterfaceIdentifier, $Interface)

            if ($IpAddress)
            {
                $IpElement = $Interface.AppendChild($UnattendBuilder.CreateElement('UnicastIpAddresses'))
                $UnattendBuilder.AddSimpleListToElement($IpAddress, 'IpAddress', $IpElement)
            }

            if ($Routes)
            {
                $RoutesElement = $Interface.AppendChild($UnattendBuilder.CreateElement('Routes'))
                for ($i = 0; $i -lt $Routes.Count; $i++)
                {
                    $RouteItem = $RoutesElement.AppendChild($UnattendBuilder.CreateElement('Route', @{action = 'add'}))
                    $Table = $Routes[$i].Clone()
                    $Table.Add('Identifier', $i)
                    $UnattendBuilder.AddHashtableValuesToElement($Table, $RouteItem)
                }
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Adds local accounts to the machine and optionally adds them to groups.
 
.DESCRIPTION
    Adds local accounts to the machine and optionally adds them to groups.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass where the account should be added.
    Valid values are:
    offlineServicing
    auditSystem
    oobeSystem (default)
 
.PARAMETER LocalAdmin
    Specifies that you are setting the local admin password.
 
.PARAMETER Name
    Specifies the name of the user to create.
 
.PARAMETER Password
    Specifies the password to set for the local account.
    If a name is not specified then this will set the local admin password.
 
.PARAMETER DisplayName
    Specifies a displayname for the new local account.
 
.PARAMETER Group
    Specifies the groups local account should be added to.
 
.PARAMETER Description
    Specifies a description for the new local account.
 
.PARAMETER PasswordAsPlainText
    Specifies that the PW should be stored as plaintext in the unattend file.
 
.PARAMETER SkipPasswordEncoding
    Skips encoding the PW for the unattend file. Useful if you want to add a PW that has already been encoded.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Add-UnattendUser
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet('offlineServicing', 'auditSystem', 'oobeSystem')]
        [string]
        $Pass = 'oobeSystem',

        [Parameter(Mandatory, ParameterSetName = 'LocalAdmin')]
        [switch]
        $LocalAdmin,

        [Parameter(Mandatory, ParameterSetName = 'LocalUser')]
        [string[]]
        $Name,

        [Parameter(ParameterSetName = 'LocalUser')]
        [Parameter(Mandatory, ParameterSetName = 'LocalAdmin')]
        [AllowEmptyString()]
        [string]
        $Password,

        [Parameter(ParameterSetName = 'LocalUser')]
        [string]
        $DisplayName,

        [Parameter(ParameterSetName = 'LocalUser')]
        [string[]]
        $Group,

        [Parameter(ParameterSetName = 'LocalUser')]
        [string]
        $Description,

        [Parameter()]
        [switch]
        $PasswordAsPlainText,

        [Parameter()]
        [switch]
        $SkipPasswordEncoding
    )
    process
    {
        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', $Pass)
        if ($LocalAdmin)
        {
            if ($Pass -eq 'offlineServicing')
            {
                $UserAccounts = $UnattendBuilder.GetOrCreateChildElement('OfflineUserAccounts', $Component)
                $AdminElement = $UnattendBuilder.GetOrCreateChildElement('OfflineAdministratorPassword', $UserAccounts)
            }
            else
            {
                $UserAccounts = $UnattendBuilder.GetOrCreateChildElement('UserAccounts', $Component)
                $AdminElement = $UnattendBuilder.GetOrCreateChildElement('AdministratorPassword', $UserAccounts)
            }

            $PW = if ($PasswordAsPlainText -or $SkipPasswordEncoding -or [string]::IsNullOrEmpty($Password))
            {
                $Password
            }
            else
            {
                if ($Pass -eq 'offlineServicing')
                {
                    EncodeUnattendPassword -Password $Password -Kind OfflineLocalAdmin
                }
                else
                {
                    EncodeUnattendPassword -Password $Password -Kind LocalAdmin
                }
            }

            $UnattendBuilder.SetElementValue('Value', $PW, $AdminElement)
            $UnattendBuilder.SetElementValue('PlainText', $PasswordAsPlainText, $AdminElement)
        }
        else
        {
            if ($Pass -eq 'offlineServicing')
            {
                $UserAccounts = $UnattendBuilder.GetOrCreateChildElement('OfflineUserAccounts', $Component)
                $LocalAccounts = $UnattendBuilder.GetOrCreateChildElement("OfflineLocalAccounts", $UserAccounts)
            }
            else
            {
                $UserAccounts = $UnattendBuilder.GetOrCreateChildElement('UserAccounts', $Component)
                $LocalAccounts = $UnattendBuilder.GetOrCreateChildElement("LocalAccounts", $UserAccounts)
            }

            foreach ($UserName in $Name)
            {
                $NewAccount = $LocalAccounts.AppendChild($UnattendBuilder.CreateElement('LocalAccount', @{action = 'add'}))
                switch ($PSBoundParameters.Keys)
                {
                    'Description'
                    {
                        $UnattendBuilder.CreateAndAppendElement('Description', $Description, $NewAccount)
                        continue
                    }
                    'DisplayName'
                    {
                        $UnattendBuilder.CreateAndAppendElement('DisplayName', $DisplayName, $NewAccount)
                        continue
                    }
                    'Group'
                    {
                        $UnattendBuilder.CreateAndAppendElement('Group', $Group -join ';', $NewAccount)
                        continue
                    }
                    'Name'
                    {
                        $UnattendBuilder.CreateAndAppendElement('Name', $UserName, $NewAccount)
                        continue
                    }
                    'Password'
                    {
                        $PasswordElement = $NewAccount.AppendChild($UnattendBuilder.CreateElement('Password'))
                        $PW = if ($PasswordAsPlainText -or $SkipPasswordEncoding -or [string]::IsNullOrEmpty($Password))
                        {
                            $Password
                        }
                        else
                        {
                            EncodeUnattendPassword -Password $Password -Kind UserAccount
                        }
                        $UnattendBuilder.CreateAndAppendElement('Value', $PW, $PasswordElement)
                        $UnattendBuilder.CreateAndAppendElement('PlainText', $PasswordAsPlainText, $PasswordElement)
                        continue
                    }
                }
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Creates an unattend file from the provided UnattendBuilder object.
 
.DESCRIPTION
    Creates an unattend file from the provided UnattendBuilder object.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that contains all the file contents.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER FilePath
    The full path to the file that should be created.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
#>

function Export-UnattendFile
{
    [CmdletBinding(PositionalBinding = $false)]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter(Mandatory, Position = 0)]
        [string]
        $FilePath
    )
    process
    {
        $ParentPath = Split-Path -LiteralPath $FilePath -ErrorAction Stop
        $ResolvedPath = Resolve-Path -LiteralPath $ParentPath -ErrorAction Stop
        if ($ResolvedPath.Provider.Name -ne 'FileSystem')
        {
            $PSCmdlet.ThrowTerminatingError(
                [ErrorRecord]::new(
                    [ArgumentException]::new("The provided path is invalid. Please provide a filesystem path.", "FilePath"),
                    "NotAFilesystemPath",
                    [ErrorCategory]::InvalidArgument,
                    $FilePath
                )
            )
        }

        $FileName = Split-Path -Path $FilePath -Leaf -ErrorAction Stop
        $OutputPath = Join-Path -Path $ResolvedPath.ProviderPath -ChildPath $FileName
        $UnattendBuilder.ToXml().Save($OutputPath)
    }
}
<#
.SYNOPSIS
    Creates a new unattendbuilder object that can be used to build an unattend file.
 
.DESCRIPTION
    Creates a new unattendbuilder object that can be used to build an unattend file.
    This command includes convenience parameters that allow you to get started with a new unattend file that includes the basics
    but you can also start from a clean slate.
    Another option is to import an existing file/XML document as a baseline, and add additional settings to it.
 
.PARAMETER SourceFile
    Specifies the path to an XML file that contains an existing unattend file to import.
 
.PARAMETER SourceDocument
    Specifies the XmlDocument object that contains an existing unattend file that should be modified.
 
.PARAMETER UiLanguage
    Specifies the display language to set in WinPE and Windows.
 
.PARAMETER SystemLocale
    Specifies the system locale to set in WinPE and Windows.
 
.PARAMETER InputLocale
    Specifies the keyboard layout to set in WinPE and Windows.
 
.PARAMETER ProductKey
    Specifies the product key to add the unattend file.
 
.PARAMETER DiskTemplate
    Specifies the predefined disk template to use during the installation
 
.PARAMETER SkipOOBE
    Skips all the OOBE windows.
 
.PARAMETER LocalAdminPassword
    Sets the local admin password.
 
.PARAMETER LocalUserToAdd
    Adds a local user as admin.
 
.PARAMETER LocalUserPassword
    Sets a password for the specified user.
 
.OUTPUTS
    [UnattendBuilder]
#>

function New-UnattendBuilder
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('FullName')]
        [string]
        $SourceFile,

        [Parameter(ValueFromPipeline)]
        [xml]
        $SourceDocument,

        [Parameter()]
        [cultureinfo]
        $UiLanguage,

        [Parameter()]
        [cultureinfo]
        $SystemLocale,

        [Parameter()]
        [cultureinfo[]]
        $InputLocale,

        [Parameter()]
        [string]
        $ProductKey,

        [Parameter()]
        [ValidateSet('BIOS', 'UEFI')]
        [string]
        $DiskTemplate,

        [Parameter()]
        [switch]
        $SkipOOBE,

        [Parameter()]
        [string]
        $LocalAdminPassword,

        [Parameter()]
        [string]
        $LocalUserToAdd,

        [Parameter()]
        [string]
        $LocalUserPassword
    )
    process
    {
        $Builder = try
        {
            if ($SourceFile)
            {
                $ResolvedPath = Resolve-Path -LiteralPath $SourceFile -ErrorAction Stop
                [UnattendBuilder]::new($ResolvedPath.ProviderPath)
            }
            elseif ($SourceDocument)
            {
                [UnattendBuilder]::new($SourceDocument)
            }
            else
            {
                [UnattendBuilder]::new()
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError($_)
        }

        $LanguageParams = @{}
        switch ($PSBoundParameters.Keys)
        {
            'UiLanguage'
            {
                $LanguageParams.Add('UiLanguage', $UiLanguage)
                continue
            }
            'SystemLocale'
            {
                $LanguageParams.Add('SystemLocale', $SystemLocale)
                continue
            }
            'InputLocale'
            {
                $LanguageParams.Add('InputLocale', $InputLocale)
                continue
            }
            'ProductKey'
            {
                $null = $Builder | Set-UnattendProductKey -ProductKey $ProductKey
                continue
            }
            'DiskTemplate'
            {
                $null = $Builder | Add-UnattendDiskPartition -Template $DiskTemplate -DiskNumber 0 | Add-UnattendImage -InstallToAvailablePartition
                continue
            }
            'SkipOOBE'
            {
                $null = $Builder | Set-UnattendWindowsSetupSetting -AcceptEula | Set-UnattendOobeSetting -HideEula -HideLocalAccount -HideOem -HideOnlineAccount -HideNetworkSetup -UseExpressSettings:$false
                continue
            }
            'LocalAdminPassword'
            {
                $null = $Builder | Add-UnattendUser -LocalAdmin -Password $LocalAdminPassword
                continue
            }
            'LocalUserToAdd'
            {
                $UserParams = @{Name = $LocalUserToAdd}
                if ($LocalUserPassword)
                {
                    $UserParams.Add("Password", $LocalUserPassword)
                }
                $null = $Builder | Add-UnattendUser @UserParams -Group Administrators
                continue
            }
        }

        if ($LanguageParams.Count -gt 0)
        {
            $null = $Builder | Set-UnattendLanguageSetting -Pass windowsPE,specialize,oobeSystem @LanguageParams
        }

        $Builder
    }
}
<#
.SYNOPSIS
    Configures audio settings.
 
.DESCRIPTION
    Configures audio settings.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER DisableSpatialOnComboEndpoints
    Not documented.
 
.PARAMETER DisableCaptureMonitor
    Prevents users from playing audio by connecting devices (music players) to the "Audio in" port.
 
.PARAMETER DisableVolumeControlOnLockscreen
    Disables volume adjustment from the lock screen.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendAudioSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [switch]
        $DisableSpatialOnComboEndpoints,

        [Parameter()]
        [switch]
        $DisableCaptureMonitor,

        [Parameter()]
        [switch]
        $DisableVolumeControlOnLockscreen
    )
    process
    {
        $Pass = 'specialize'

        switch ($PSBoundParameters.Keys)
        {
            'DisableSpatialOnComboEndpoints'
            {
                $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Audio-AudioCore', $Pass)
                $UnattendBuilder.SetElementValue('DisableSpatialOnComboEndpoints', $DisableSpatialOnComboEndpoints, $Component)
                continue
            }
            'DisableCaptureMonitor'
            {
                $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Audio-AudioCore', $Pass)
                $UnattendBuilder.SetElementValue('EnableCaptureMonitor', !$DisableCaptureMonitor, $Component)
                continue
            }
            'DisableVolumeControlOnLockscreen'
            {
                $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Audio-VolumeControl', $Pass)
                $UnattendBuilder.SetElementValue('EnableVolumeControlWhileLocked', !$DisableVolumeControlOnLockscreen, $Component)
                continue
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures the autologon user details, and whether or not autologon is enabled.
 
.DESCRIPTION
    Configures the autologon user details, and whether or not autologon is enabled.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass this command should apply to.
    Valid values are:
    specialize
    auditSystem
    oobeSystem (default)
 
.PARAMETER UserDomain
    Specifies the domain that the autologon user is a member of.
 
.PARAMETER UserName
    Specifies the username of the autologon user.
 
.PARAMETER Password
    Specifies the password of the autologon user.
 
.PARAMETER PasswordAsPlainText
    Specifies that the password should be stored as plaintext in the unattend file.
 
.PARAMETER SkipPasswordEncoding
    Specifies that this command should not encode the password, useful if you want to add a password that has already been encoded.
 
.PARAMETER DisableAutoLogon
    Disables autologon.
 
.PARAMETER LogonCount
    Specifies how many times the user should log in automatically.
    This is useful when running multiple setup scripts that require reboots.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendAutoLogon
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet("specialize", 'auditSystem', 'oobeSystem')]
        [string[]]
        $Pass = "oobeSystem",

        [Parameter()]
        [string]
        $UserDomain,

        [Parameter(Mandatory)]
        [string]
        $UserName,

        [Parameter()]
        [string]
        $Password,

        [Parameter()]
        [switch]
        $PasswordAsPlainText,

        [Parameter()]
        [switch]
        $SkipPasswordEncoding,

        [Parameter()]
        [switch]
        $DisableAutoLogon,

        [Parameter()]
        [uint32]
        $LogonCount
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', $PassName)
            $AutoLogon = $UnattendBuilder.GetOrCreateChildElement('AutoLogon', $Component)
            switch ($PSBoundParameters.Keys)
            {
                'UserDomain'
                {
                    $UnattendBuilder.SetElementValue("Domain", $UserDomain, $AutoLogon)
                    continue
                }
                'UserName'
                {
                    $UnattendBuilder.SetElementValue("Username", $UserName, $AutoLogon)
                    continue
                }
                'Password'
                {
                    $PasswordElement = $UnattendBuilder.GetOrCreateChildElement('Password', $AutoLogon)
                    $PW = if ($PasswordAsPlainText -or $SkipPasswordEncoding)
                    {
                        $Password
                    }
                    else
                    {
                        EncodeUnattendPassword -Password $Password -Kind UserAccount
                    }
                    $UnattendBuilder.SetElementValue('Value', $PW, $PasswordElement)
                    $UnattendBuilder.SetElementValue('PlainText', $PasswordAsPlainText, $PasswordElement)
                }
                'LogonCount'
                {
                    $UnattendBuilder.SetElementValue("LogonCount", $LogonCount, $AutoLogon)
                    continue
                }
            }

            $UnattendBuilder.SetElementValue("Enabled", !$DisableAutoLogon, $AutoLogon)
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Sets the computer computer name to be set during installation.
 
.DESCRIPTION
    Sets the computer computer name to be set during installation.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    The pass that this command should apply to.
    Supported values are:
    offlineServicing
    specialize (default)
 
.PARAMETER ComputerName
    The computer name to set during installation.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendComputerName
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet("offlineServicing", "specialize")]
        [string[]]
        $Pass = "specialize",

        [Parameter(Mandatory, Position = 0)]
        [string]
        $ComputerName
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', $PassName)
            $UnattendBuilder.SetElementValue('ComputerName', $ComputerName, $Component)
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures global DNS settings.
 
.DESCRIPTION
    Configures global DNS settings.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass this command should apply to.
    Valid values are:
    windowsPE
    specialize (Default)
 
.PARAMETER DnsDomain
   Specifies the primary DNS domain to be used for name resolution.
   This will be used for DNS client registrations and DNS client resolution if no suffixes have been configured.
 
.PARAMETER DisableDomainNameDevolution
     Specifies that the name resolver does not use domain-name devolution.
 
.PARAMETER DnsSuffixSearchOrder
    Specifies the DNS suffixes to use when attempting to resolve hostnames without a domain.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendDnsSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet('windowsPE', 'specialize')]
        [string[]]
        $Pass = 'specialize',

        [Parameter()]
        [string]
        $DnsDomain,

        [Parameter()]
        [switch]
        $DisableDomainNameDevolution,

        [Parameter()]
        [string[]]
        $DnsSuffixSearchOrder
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-DNS-Client', $PassName)
            switch ($PSBoundParameters.Keys)
            {
                'DnsDomain'
                {
                    $UnattendBuilder.SetElementValue('DNSDomain', $DnsDomain, $Component)
                    continue
                }
                'DisableDomainNameDevolution'
                {
                    $UnattendBuilder.SetElementValue('UseDomainNameDevolution', !$DisableDomainNameDevolution, $Component)
                    continue
                }
                'DnsSuffixSearchOrder'
                {
                    $SuffixElement = $UnattendBuilder.GetOrCreateChildElement('DNSSuffixSearchOrder', $Component)
                    if ($SuffixElement.HasChildNodes)
                    {
                        $SuffixElement.RemoveAll()
                    }
                    $UnattendBuilder.AddSimpleListToElement($DnsSuffixSearchOrder, 'DomainName', $SuffixElement)
                    continue
                }
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures settings related to joining a domain or workgroup.
 
.DESCRIPTION
    Configures settings related to joining a domain or workgroup.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER OfflinePass
    Specifies that this should happen during the offline pass, otherwise it will happen during the specialization phase.
 
.PARAMETER UnsecureJoin
    Specifies whether to add the computer to the domain without requiring a unique password.
    UnsecureJoin is performed, by using a null session with a pre-existing account.
    This means there is no authentication to the domain controller when configuring the machine account; it is done anonymously.
    The account must have a well-known password or a specified value for MachinePassword.
    The well-known password is the first 14 characters of the computer name in lower case.
 
.PARAMETER AccountData
    Specifies the base64 string containing join details that has been generated by djoin.exe
 
.PARAMETER JoinCredential
    Specifies the credentials to use to join the domain.
 
.PARAMETER DomainName
    Specifies the name of the domain to join.
 
.PARAMETER WorkgroupName
    Specifies the workgroup name of the workgroup to join.
 
.PARAMETER DebugJoin
    Specifies a trigger to run the debugging routine if setup encounters an error code.
    This setting enables you to debug Windows Setup failures.
 
.PARAMETER DebugJoinError
    Specifies a particular error code that causes DebugJoin to trigger if encountered during Windows Setup.
 
.PARAMETER TargetOU
    Specifies the target OU to place the computer object in after the domain join.
 
.PARAMETER MachinePassword
    MachinePassword is used with UnsecureJoin, which is performed by using a null session with a pre-existing account.
    This means there is no authentication to the domain controller when configuring the computer account.
    It is done anonymously.
    The account must have a well-known password or a specified MachinePassword.
    The well-known password is the first 14 characters of the computer name in lowercase.
 
.PARAMETER TimeoutInMinutes
    Specifies how long Windows will wait until it gives up joining the domain.
    Valid values are between 5 and 60 minutes.
    Default is 15 minutes.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendDomainJoinInfo
{
    [CmdletBinding(DefaultParameterSetName = 'Workgroup', PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter(Mandatory, ParameterSetName = 'DomainJoinOfflinePass')]
        [switch]
        $OfflinePass,

        [Parameter(Mandatory, ParameterSetName = 'UnsecureDomainJoin')]
        [switch]
        $UnsecureJoin,

        [Parameter(Mandatory, ParameterSetName = 'DomainJoinOfflinePass')]
        [Parameter(Mandatory, ParameterSetName = 'DomainJoinPreprovisioned')]
        [string]
        $AccountData,

        [Parameter(Mandatory, ParameterSetName = 'DomainJoin')]
        [pscredential]
        $JoinCredential,

        [Parameter(Mandatory, ParameterSetName = 'DomainJoinPreprovisioned')]
        [Parameter(Mandatory, ParameterSetName = 'DomainJoin')]
        [Parameter(Mandatory, ParameterSetName = 'UnsecureDomainJoin')]
        [string]
        $DomainName,

        [Parameter(ParameterSetName = 'Workgroup')]
        [string]
        $WorkgroupName,

        [Parameter(ParameterSetName = 'DomainJoinPreprovisioned')]
        [Parameter(ParameterSetName = 'DomainJoin')]
        [Parameter(ParameterSetName = 'UnsecureDomainJoin')]
        [switch]
        $DebugJoin,

        [Parameter(ParameterSetName = 'DomainJoinPreprovisioned')]
        [Parameter(ParameterSetName = 'DomainJoin')]
        [Parameter(ParameterSetName = 'UnsecureDomainJoin')]
        [string]
        $DebugJoinError,

        [Parameter(ParameterSetName = 'DomainJoinPreprovisioned')]
        [Parameter(ParameterSetName = 'DomainJoin')]
        [Parameter(ParameterSetName = 'UnsecureDomainJoin')]
        [string]
        $TargetOU,

        [Parameter(Mandatory, ParameterSetName = 'UnsecureDomainJoin')]
        [string]
        $MachinePassword,

        [Parameter(ParameterSetName = 'DomainJoinPreprovisioned')]
        [Parameter(ParameterSetName = 'DomainJoin')]
        [Parameter(ParameterSetName = 'UnsecureDomainJoin')]
        [ValidateRange(5, 60)]
        [int]
        $TimeoutInMinutes
    )
    process
    {
        if ($PSCmdlet.ParameterSetName -eq 'DomainJoinOfflinePass')
        {
            $PassName = 'offlineServicing'
            $ChildElementName = 'OfflineIdentification'
        }
        else
        {
            $PassName = 'specialize'
            $ChildElementName = 'Identification'
        }

        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-UnattendedJoin', $PassName)
        $IdentificationElement = $UnattendBuilder.GetOrCreateChildElement($ChildElementName, $Component)

        switch ($PSBoundParameters.Keys)
        {
            'UnsecureJoin'
            {
                $UnattendBuilder.SetElementValue('UnsecureJoin', $UnsecureJoin, $IdentificationElement)
                continue
            }
            'AccountData'
            {
                $ProvisioningElement = $UnattendBuilder.GetOrCreateChildElement('Provisioning', $IdentificationElement)
                $UnattendBuilder.SetElementValue('AccountData', $AccountData, $ProvisioningElement)
                continue
            }
            'JoinCredential'
            {
                $UnattendBuilder.SetCredentialOnElement($JoinCredential, $IdentificationElement)
                continue
            }
            'DomainName'
            {
                $UnattendBuilder.SetElementValue('JoinDomain', $DomainName, $IdentificationElement)
                continue
            }
            'WorkgroupName'
            {
                $UnattendBuilder.SetElementValue('JoinWorkgroup', $WorkgroupName, $IdentificationElement)
                continue
            }
            'DebugJoin'
            {
                $UnattendBuilder.SetElementValue('DebugJoin', $DebugJoin, $IdentificationElement)
                continue
            }
            'DebugJoinError'
            {
                $UnattendBuilder.SetElementValue('DebugJoinOnlyOnThisError', $DebugJoinError, $IdentificationElement)
                continue
            }
            'TargetOU'
            {
                $UnattendBuilder.SetElementValue('MachineObjectOU', $TargetOU, $IdentificationElement)
                continue
            }
            'MachinePassword'
            {
                $UnattendBuilder.SetElementValue('MachinePassword', $MachinePassword, $IdentificationElement)
                continue
            }
            'TimeoutInMinutes'
            {
                $UnattendBuilder.SetElementValue('TimeoutPeriodInMinutes', $TimeoutInMinutes, $IdentificationElement)
                continue
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures firewall settings.
 
.DESCRIPTION
    Configures firewall settings, these firewall settings apply to the installed OS, not WinPE.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER DisableStatefulFTP
    Disables the FTP inspection engine.
    On server editions this is turned on by default, and setting this switch will disable it.
    On client editions this is turned off by default, but can be enabled by setting this switch to false: -DisableStatefulFTP:$false
 
.PARAMETER DisableStatefulPPTP
    Disables the point to point tunneling inspection.
    On server editions this is turned on by default, and setting this switch will disable it.
    On client editions this is turned off by default, but can be enabled by setting this switch to false: -DisableStatefulPPTP:$false
 
.PARAMETER FirewallProfile
    Specifies the firewall profile the profile specific settings should apply to.
 
.PARAMETER DisableFirewall
    Disables the specified firewall profile.
 
.PARAMETER DisableNotifications
    Disables notifications about programs being blocked.
 
.PARAMETER LogDroppedPackets
    Enables logging of dropped packets.
 
.PARAMETER LogSuccessfulConnections
    Enables logging of allowed connections, by default only dropped connections are logged.
 
.PARAMETER LogFilePath
    Specifies the filepath of the logfile for this profile.
 
.PARAMETER LogFileSizeKB
    Specifies how big the logfile can be.
 
.PARAMETER EnabledFirewallGroups
    Specifies the firewall groups to enable.
    Firewall group names can be found with this command: Get-NetFirewallRule | select Name,DisplayName,Group
 
.PARAMETER DisabledFirewallGroups
    Specifies the firewall groups to disable.
    Firewall group names can be found with this command: Get-NetFirewallRule | select Name,DisplayName,Group
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendFirewallSetting
{
    [CmdletBinding(DefaultParameterSetName = 'GlobalSettings', PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter(ParameterSetName = "GlobalSettings")]
        [Parameter(ParameterSetName = "ProfileSpecific")]
        [switch]
        $DisableStatefulFTP,

        [Parameter(ParameterSetName = "GlobalSettings")]
        [Parameter(ParameterSetName = "ProfileSpecific")]
        [switch]
        $DisableStatefulPPTP,

        [Parameter(Mandatory, ParameterSetName = "ProfileSpecific")]
        [ValidateSet("Domain", 'Private', 'Public', 'All')]
        [string]
        $FirewallProfile,

        [Parameter(ParameterSetName = "ProfileSpecific")]
        [switch]
        $DisableFirewall,

        [Parameter(ParameterSetName = "ProfileSpecific")]
        [switch]
        $DisableNotifications,

        [Parameter(ParameterSetName = "ProfileSpecific")]
        [switch]
        $LogDroppedPackets,

        [Parameter(ParameterSetName = "ProfileSpecific")]
        [switch]
        $LogSuccessfulConnections,

        [Parameter(ParameterSetName = "ProfileSpecific")]
        [string]
        $LogFilePath,

        [Parameter(ParameterSetName = "ProfileSpecific")]
        [int]
        $LogFileSizeKB,

        [Parameter(ParameterSetName = "ProfileSpecific")]
        [string[]]
        $EnabledFirewallGroups,

        [Parameter(ParameterSetName = "ProfileSpecific")]
        [string[]]
        $DisabledFirewallGroups
    )
    process
    {
        $Component = $UnattendBuilder.GetOrCreateComponent('Networking-MPSSVC-Svc', 'specialize')
        if ($EnabledFirewallGroups -or $DisabledFirewallGroups)
        {
            $GroupsElement = $UnattendBuilder.GetOrCreateChildElement('FirewallGroups', $Component)
            $FwCommonParams = @{
                UnattendBuilder = $UnattendBuilder
                Parent          = $GroupsElement
                FirewallProfile = $FirewallProfile.ToLower()
            }
            if ($EnabledFirewallGroups)
            {
                AddFirewallGroupsToElement @FwCommonParams -GroupNames $EnabledFirewallGroups -Active $true
            }
            if ($DisabledFirewallGroups)
            {
                AddFirewallGroupsToElement @FwCommonParams -GroupNames $DisabledFirewallGroups -Active $false
            }
        }

        if ($PSBoundParameters.ContainsKey('DisableStatefulFTP'))
        {
            $UnattendBuilder.SetElementValue('DisableStatefulFTP', $DisableStatefulFTP, $Component)
        }
        if ($PSBoundParameters.ContainsKey('DisableStatefulPPTP'))
        {
            $UnattendBuilder.SetElementValue('DisableStatefulPPTP', $DisableStatefulPPTP, $Component)
        }

        $FwProfiles = if ($FirewallProfile -eq "All")
        {
            "Domain", 'Private', 'Public'
        }
        else
        {
            $FirewallProfile
        }

        foreach ($Item in $FwProfiles)
        {
            switch ($PSBoundParameters.Keys)
            {
                'DisableFirewall'
                {
                    $UnattendBuilder.SetElementValue("${Item}Profile_EnableFirewall", !$DisableFirewall, $Component)
                    continue
                }
                'DisableNotifications'
                {
                    $UnattendBuilder.SetElementValue("${Item}Profile_DisableNotifications", $DisableNotifications, $Component)
                    continue
                }
                'LogDroppedPackets'
                {
                    $UnattendBuilder.SetElementValue("${Item}Profile_LogDroppedPackets", $LogDroppedPackets, $Component)
                    continue
                }
                'LogSuccessfulConnections'
                {
                    $UnattendBuilder.SetElementValue("${Item}Profile_LogSuccessfulConnections", $LogSuccessfulConnections, $Component)
                    continue
                }
                'LogFilePath'
                {
                    $UnattendBuilder.SetElementValue("${Item}Profile_LogFile", $LogFilePath, $Component)
                    continue
                }
                'LogFileSizeKB'
                {
                    $UnattendBuilder.SetElementValue("${Item}Profile_LogFileSize", $LogFileSizeKB, $Component)
                    continue
                }
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures global IP settings.
 
.DESCRIPTION
    Configures global IP settings.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass to use.
    Valid values are:
    windowsPE
    specialize (default)
 
.PARAMETER DisableIcmpRedirects
    Specifies that the IPv4 and IPv6 path caches are not updated in response to ICMP redirect messages.
    This is a global setting that applies to all interfaces.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendIpSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet('windowsPE', 'specialize')]
        [string[]]
        $Pass = 'specialize',

        [Parameter()]
        [switch]
        $DisableIcmpRedirects
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-TCPIP', $PassName)

            if ($PSBoundParameters.ContainsKey('DisableIcmpRedirects'))
            {
                $UnattendBuilder.SetElementValue('IcmpRedirectsEnabled', !$DisableIcmpRedirects, $Component)
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures language and localization settings.
 
.DESCRIPTION
    Configures language and localization settings.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass this command should apply to.
    Valid values are:
    windowsPE
    specialize (Default)
    oobeSystem
 
.PARAMETER InputLocale
    Specifies the keyboard layout that should be used, for example: da-DK
 
.PARAMETER SystemLocale
    Specifies the language to use for non-unicode programs. Can be specified like this: da-DK
 
.PARAMETER UiLanguage
    Specifies the language of the shell. Can be specified like this: da-DK
 
.PARAMETER SetupUiLanguage
    Specifies the language to use in the WinPE setup UI.
 
.PARAMETER UiLanguageFallback
    The fallback language of the shell, for components that have not been localized in the primary language.
    Can be specified like this: en-US
 
.PARAMETER UserLocale
    Specifies the format used for dates, currency and other localized content.
    Can be specified like this: da-DK
 
.PARAMETER LayeredDriver
    The keyboard driver used in WinPE for asian languages.
    Valid values are 1-6.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendLanguageSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet('windowsPE', 'specialize', 'oobeSystem')]
        [string[]]
        $Pass = 'specialize',

        [Parameter()]
        [cultureinfo[]]
        $InputLocale,

        [Parameter()]
        [cultureinfo]
        $SystemLocale,

        [Parameter()]
        [cultureinfo]
        $UiLanguage,

        [Parameter()]
        [cultureinfo]
        $SetupUiLanguage,

        [Parameter()]
        [cultureinfo]
        $UiLanguageFallback,

        [Parameter()]
        [cultureinfo]
        $UserLocale,

        [Parameter()]
        [ValidateRange(1, 6)]
        [int]
        $LayeredDriver
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            $ComponentName = if ($PassName -eq "windowsPE")
            {
                'Microsoft-Windows-International-Core-WinPE'
            }
            else
            {
                'Microsoft-Windows-International-Core'
            }

            $Component = $UnattendBuilder.GetOrCreateComponent($ComponentName, $PassName)
            switch ($PSBoundParameters.Keys)
            {
                'InputLocale'
                {
                    $UnattendBuilder.SetElementValue('InputLocale', $InputLocale.Name -join ';', $Component)
                    continue
                }
                'SystemLocale'
                {
                    $UnattendBuilder.SetElementValue('SystemLocale', $SystemLocale.Name, $Component)
                    continue
                }
                'UiLanguage'
                {
                    $UnattendBuilder.SetElementValue('UILanguage', $UiLanguage.Name, $Component)
                    continue
                }
                'UiLanguageFallback'
                {
                    $UnattendBuilder.SetElementValue('UILanguageFallback', $UiLanguageFallback.Name, $Component)
                    continue
                }
                'UserLocale'
                {
                    $UnattendBuilder.SetElementValue('UserLocale', $UserLocale.Name, $Component)
                    continue
                }
                'SetupUiLanguage'
                {
                    if ($PassName -eq "windowsPE")
                    {
                        $SetupUIElement = $UnattendBuilder.GetOrCreateChildElement('SetupUILanguage', $Component)
                        $UnattendBuilder.SetElementValue('UILanguage', $SetupUiLanguage, $SetupUIElement)
                    }
                    else
                    {
                        Write-Warning -Message "$_ can only be set on windowsPE pass. Ignoring it for pass: $PassName"
                    }
                    continue
                }
                'LayeredDriver'
                {
                    if ($PassName -eq "windowsPE")
                    {
                        $UnattendBuilder.SetElementValue('LayeredDriver', $LayeredDriver, $ComponentName)
                    }
                    else
                    {
                        Write-Warning -Message "$_ can only be set on windowsPE pass. Ignoring it for pass: $PassName"
                    }
                    continue
                }
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures settings related to the OOBE.
 
.DESCRIPTION
    Configures settings related to the Out Of Box Experience.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER HideEula
    Skips the EULA page (With the implication that you accept the EULA)
 
.PARAMETER HideLocalAccount
    Skips the page to set the password for the Administrator account on server editions.
 
.PARAMETER HideOem
    Skips any OEM specific page.
 
.PARAMETER HideOnlineAccount
    Skips the online account sign in/creation screen.
 
.PARAMETER HideNetworkSetup
    Skips the network setup page.
 
.PARAMETER UseExpressSettings
    Skips the pages related to express settings.
    When this switch is set, express settings will be turned on, and the page will be skipped.
    When this switch is explicitly turned off (by specifying the parameter like this: -UseExpressSettings:$false)
    Express settings will be turned off, and the page will be skipped.
    If this is not set then the page will be shown during OOBE.
 
.PARAMETER SkipMachineOOBE
    Is supposedly deprecated but skips the OOBE.
 
.PARAMETER SkipUserOOBE
    Is supposedly deprecated but skips the OOBE.
 
.PARAMETER NetworkLocation
    Sets the network location.
    Valid values are:
    Home
    Work
    Other
 
.PARAMETER SkipAdminProfileRemoval
    Skip removing the default administrator account profile.
 
.PARAMETER SkipLanguageChange
    Skips notifying windows about language changes during the OOBE.
 
.PARAMETER SkipWinReInitialization
    Skips setting up Win RE during the OOBE.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendOobeSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [switch]
        $HideEula,

        [Parameter()]
        [switch]
        $HideLocalAccount,

        [Parameter()]
        [switch]
        $HideOem,

        [Parameter()]
        [switch]
        $HideOnlineAccount,

        [Parameter()]
        [switch]
        $HideNetworkSetup,

        [Parameter()]
        [switch]
        $UseExpressSettings,

        [Parameter()]
        [switch]
        $SkipMachineOOBE,

        [Parameter()]
        [switch]
        $SkipUserOOBE,

        [Parameter()]
        [ValidateSet('Home', 'Work', 'Other')]
        [string]
        $NetworkLocation,

        [Parameter()]
        [switch]
        $SkipAdminProfileRemoval,

        [Parameter()]
        [switch]
        $SkipLanguageChange,

        [Parameter()]
        [switch]
        $SkipWinReInitialization
    )
    process
    {
        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', 'oobeSystem')
        $OOBE = $UnattendBuilder.GetOrCreateChildElement('OOBE', $Component)
        switch ($PSBoundParameters.Keys)
        {
            'HideEula'
            {
                $UnattendBuilder.SetElementValue('HideEULAPage', $HideEula, $OOBE)
                continue
            }
            'HideLocalAccount'
            {
                $UnattendBuilder.SetElementValue('HideLocalAccountScreen', $HideLocalAccount, $OOBE)
                continue
            }
            'HideOem'
            {
                $UnattendBuilder.SetElementValue('HideOEMRegistrationScreen', $HideOem, $OOBE)
                continue
            }
            'HideOnlineAccount'
            {
                $UnattendBuilder.SetElementValue('HideOnlineAccountScreens', $HideOnlineAccount, $OOBE)
                continue
            }
            'HideNetworkSetup'
            {
                $UnattendBuilder.SetElementValue('HideWirelessSetupInOOBE', $HideNetworkSetup, $OOBE)
                continue
            }
            'UseExpressSettings'
            {
                $Value = if ($UseExpressSettings)
                {
                    1
                }
                else
                {
                    3
                }
                $UnattendBuilder.SetElementValue('ProtectYourPC', $Value, $OOBE)
                continue
            }
            'SkipMachineOOBE'
            {
                $UnattendBuilder.SetElementValue('SkipMachineOOBE', $SkipMachineOOBE, $OOBE)
                continue
            }
            'SkipUserOOBE'
            {
                $UnattendBuilder.SetElementValue('SkipUserOOBE', $SkipUserOOBE, $OOBE)
                continue
            }
            'NetworkLocation'
            {
                $UnattendBuilder.SetElementValue('NetworkLocation', $NetworkLocation, $OOBE)
                continue
            }
            'SkipAdminProfileRemoval'
            {
                $VmOptimizations = $UnattendBuilder.GetOrCreateChildElement('VMModeOptimizations', $OOBE)
                $UnattendBuilder.SetElementValue('SkipAdministratorProfileRemoval', $SkipAdminProfileRemoval, $VmOptimizations)
                continue
            }
            'SkipLanguageChange'
            {
                $VmOptimizations = $UnattendBuilder.GetOrCreateChildElement('VMModeOptimizations', $OOBE)
                $UnattendBuilder.SetElementValue('SkipNotifyUILanguageChange', $SkipLanguageChange, $VmOptimizations)
                continue
            }
            'SkipWinReInitialization'
            {
                $VmOptimizations = $UnattendBuilder.GetOrCreateChildElement('VMModeOptimizations', $OOBE)
                $UnattendBuilder.SetElementValue('SkipWinREInitialization', $SkipWinReInitialization, $VmOptimizations)
                continue
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Adds information about the user and organization for Windows to the unattend file.
 
.DESCRIPTION
    Adds information about the user and organization for Windows to the unattend file.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass this command applies to.
    By default, this is applied to the windowsPE and specialize phases.
    Valid values are:
    windowsPE
    offlineServicing
    generalize
    specialize
    auditUser
    oobeSystem
 
.PARAMETER Owner
    Sets the name of the end user of the computer.
 
.PARAMETER Organization
    Sets the organization that the computer belongs to.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendOwnerInfo
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet("windowsPE", "offlineServicing", "generalize", "specialize", "auditUser", "oobeSystem")]
        [string[]]
        $Pass = ("windowsPE", "specialize"),

        [Parameter()]
        [string]
        $Owner,

        [Parameter()]
        [string]
        $Organization
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            if ($PassName -eq "windowsPE")
            {
                $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Setup', $PassName)
                $UserData = $UnattendBuilder.GetOrCreateChildElement('UserData', $Component)
                switch ($PSBoundParameters.Keys)
                {
                    'Owner'
                    {
                        $UnattendBuilder.SetElementValue('FullName', $Owner, $UserData)
                        continue
                    }
                    'Organization'
                    {
                        $UnattendBuilder.SetElementValue('Organization', $Organization, $UserData)
                        continue
                    }
                }
            }
            else
            {
                $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', $PassName)
                switch ($PSBoundParameters.Keys)
                {
                    'Owner'
                    {
                        $UnattendBuilder.SetElementValue('RegisteredOwner', $Owner, $Component)
                        continue
                    }
                    'Organization'
                    {
                        $UnattendBuilder.SetElementValue('RegisteredOrganization', $Organization, $Component)
                        continue
                    }
                }
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures power settings.
 
.DESCRIPTION
    Configures power settings.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass the command should apply to.
    Supported values are:
    generalize
    specialize (default)
 
.PARAMETER PowerPlan
    The GUID of the powerplan that should be set
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendPowerSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet('generalize', 'specialize')]
        [string[]]
        $Pass = 'specialize',

        [Parameter(Mandatory)]
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
            $TrimmedWord = $wordToComplete.Trim(("'",'"'))
            $PowerplanTable = @{
                PowerSaver          = 'a1841308-3541-4fab-bc81-f71556f20b4a'
                Balanced            = '381b4222-f694-41f0-9685-ff5bb260df2e'
                HighPerformance     = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'
                UltimatePerformance = 'e9a42b02-d5df-448d-aa00-03f14749eb61'
            }
            foreach ($Key in $PowerplanTable.Keys)
            {
                if ($Key -like "$TrimmedWord*")
                {
                    $CompletionText = $PowerplanTable[$Key]
                    $ListItemText   = $Key
                    $ResultType     = [System.Management.Automation.CompletionResultType]::ParameterValue
                    $ToolTip        = $Key
                    [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $ToolTip)
                }
            }
        })]
        [guid]
        $PowerPlan
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-powercpl', $PassName)
            $UnattendBuilder.SetElementValue('PreferredPlan', $PowerPlan.Guid, $Component)
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Sets the product key to use during installation.
 
.DESCRIPTION
    Sets the product key to use during installation.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    The pass this command should apply to.
    By default, this command will add the key to all supported install phases.
    Supported values:
    windowsPE
    specialize
 
.PARAMETER ProductKey
    The product key to install.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendProductKey
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet("windowsPE", "specialize")]
        [string[]]
        $Pass = ("windowsPE", "specialize"),

        [Parameter(Mandatory, Position = 0)]
        [string]
        $ProductKey
    )
    process
    {
        switch ($Pass)
        {
            'windowsPE'
            {
                $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Setup', $_)
                $UserData = $UnattendBuilder.GetOrCreateChildElement("UserData", $Component)
                $KeyElement = $UnattendBuilder.GetOrCreateChildElement('ProductKey', $UserData)
                $UnattendBuilder.SetElementValue('Key', $ProductKey, $KeyElement)
                continue
            }
            'specialize'
            {
                $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', $_)
                $UnattendBuilder.SetElementValue('ProductKey', $ProductKey, $Component)
                continue
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures RDP settings.
 
.DESCRIPTION
    Configures RDP settings.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass this should apply to.
    Valid values are:
    offlineServicing
    generalize
    specialize (default)
 
.PARAMETER EnableRDP
    Enables RDP connections to this computer.
 
.PARAMETER AllowArbitraryRemoteApps
    Allows remote users to launch remote apps that haven't been explicitly whitelisted on this computer.
 
.PARAMETER DisableNLA
    Disables Network Level Authentication when connecting to this computer.
 
.PARAMETER SecurityLayer
    Sets the security layer used when connecting to this computer.
    Valid values are:
    RDP - The RDP protocol is used.
    Negotiate - Client and server negotiates the most secure protocol supported by both.
    TLS - Forces the protocol to use TLS.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendRdpSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet('offlineServicing', 'generalize', 'specialize')]
        [string[]]
        $Pass = 'specialize',

        [Parameter()]
        [switch]
        $EnableRDP,

        [Parameter()]
        [switch]
        $AllowArbitraryRemoteApps,

        [Parameter()]
        [switch]
        $DisableNLA,

        [Parameter()]
        [RdpSecurityLayer]
        $SecurityLayer
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            switch ($PSBoundParameters.Keys)
            {
                'EnableRDP'
                {
                    $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-TerminalServices-LocalSessionManager', $PassName)
                    $UnattendBuilder.SetElementValue('fDenyTSConnections', !$EnableRDP, $Component)
                    continue
                }
                'AllowArbitraryRemoteApps'
                {
                    $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-TerminalServices-Publishing-WMIProvider', $PassName)
                    $UnattendBuilder.SetElementValue('fDisabledAllowList', $AllowArbitraryRemoteApps, $Component)
                    continue
                }
                'DisableNLA'
                {
                    if ($PassName -ne "offlineServicing")
                    {
                        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-TerminalServices-RDP-WinStationExtensions', $PassName)
                        $UnattendBuilder.SetElementValue('UserAuthentication', (!$DisableNLA).ToInt32($null), $Component)
                    }
                    else
                    {
                        Write-Warning -Message "$_ cannot be set in offlineServicing pass. Ignoring it for this pass."
                    }

                    continue
                }
                'SecurityLayer'
                {
                    if ($PassName -ne "offlineServicing")
                    {
                        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-TerminalServices-RDP-WinStationExtensions', $PassName)
                        $UnattendBuilder.SetElementValue('SecurityLayer', $SecurityLayer.value__, $Component)
                    }
                    else
                    {
                        Write-Warning -Message "$_ cannot be set in offlineServicing pass. Ignoring it for this pass."
                    }

                    continue
                }
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures server manager settings.
 
.DESCRIPTION
    Configures server manager settings.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass this command should apply to.
    Supported values:
    generalize
    specialize (default)
 
.PARAMETER DontOpenServerManagerAtLogon
    Stops server manager from opening by default when a user logs on.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendServerManagerSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet('generalize', 'specialize')]
        [string[]]
        $Pass = 'specialize',

        [Parameter(Mandatory)]
        [switch]
        $DontOpenServerManagerAtLogon
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-ServerManager-SvrMgrNc', $PassName)
            $UnattendBuilder.SetElementValue('DoNotOpenServerManagerAtLogon', $DontOpenServerManagerAtLogon, $Component)
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures sysprep behavior for devices and device drivers.
 
.DESCRIPTION
    Configures sysprep behavior for devices and device drivers.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER DontCleanNonPresentDevices
    Specifies whether Plug and Play information persists on the destination computer during the following specialize configuration pass.
 
.PARAMETER PersistAllDeviceInstalls
    Specifies whether all Plug and Play information persists on the destination computer during the generalize configuration pass.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendSysPrepSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [switch]
        $DontCleanNonPresentDevices,

        [Parameter()]
        [switch]
        $PersistAllDeviceInstalls
    )
    process
    {
        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-PnpSysprep', 'generalize')
        switch ($PSBoundParameters.Keys)
        {
            'DontCleanNonPresentDevices'
            {
                $UnattendBuilder.SetElementValue('DoNotCleanUpNonPresentDevices', $DontCleanNonPresentDevices, $Component)
                continue
            }
            'PersistAllDeviceInstalls'
            {
                $UnattendBuilder.SetElementValue('PersistAllDeviceInstalls', $PersistAllDeviceInstalls, $Component)
                continue
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures time settings.
 
.DESCRIPTION
    Configures time settings.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER Pass
    Specifies the pass this command should apply to.
    Supported values:
    specialize (default)
    auditSystem
    oobeSystem
 
.PARAMETER TimeZone
    The ID of the timezone to set during installation.
    The available timzones can be listed with the following PS command: [System.TimeZoneInfo]::GetSystemTimeZones()
 
.PARAMETER DisableAutoDaylight
    Specifies that daylight savings should not be applied automatically by Windows.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendTimeSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [ValidateSet("specialize", "auditSystem", "oobeSystem")]
        [string[]]
        $Pass = "specialize",

        [Parameter()]
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
            $TrimmedWord = $wordToComplete.Trim(("'",'"'))

            foreach ($Timezone in [System.TimeZoneInfo]::GetSystemTimeZones())
            {
                if ($Timezone.Id -like "$TrimmedWord*")
                {
                    $CompletionText = "'$($Timezone.Id)'"
                    $ListItemText   = $Timezone.Id
                    $ResultType     = [System.Management.Automation.CompletionResultType]::ParameterValue
                    $ToolTip        = $Timezone.DisplayName
                    [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $ToolTip)
                }
            }
        })]
        [string]
        $TimeZone,

        [Parameter()]
        [switch]
        $DisableAutoDaylight
    )
    process
    {
        foreach ($PassName in $Pass)
        {
            $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Shell-Setup', $PassName)
            switch ($PSBoundParameters.Keys)
            {
                'TimeZone'
                {
                    $UnattendBuilder.SetElementValue('TimeZone', $TimeZone, $Component)
                    continue
                }
                'DisableAutoDaylight'
                {
                    $UnattendBuilder.SetElementValue('DisableAutoDaylightTimeSet', $DisableAutoDaylight, $Component)
                    continue
                }
            }
        }

        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures TPM settings.
 
.DESCRIPTION
    Configures Trusted Platform Module settings.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER ClearBehavior
    Controls under which circumstances the TPM should be cleared.
    Clearing the TPM will delete all the keys stored on the TPM, such as bitlocker keys or Windows Hello PINs.
    Valid values:
    Never - Does not clear the TPM (Default behavior).
    WhenOwner - Clears the TPM if Windows has taken ownership of the TPM.
    Always - Always clears the TPM.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendTpmSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter(Mandatory)]
        [TpmClearBehavior]
        $ClearBehavior
    )
    process
    {
        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-TPM-Tasks', 'specialize')
        $UnattendBuilder.SetElementValue('ClearTpm', $ClearBehavior.value__, $Component)
        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures UAC settings.
 
.DESCRIPTION
    Configures User Account Control settings (previously known as Limited User Account).
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER DisableUAC
    Disables UAC.
    Disabling UAC means that any program run by privileged accounts will run elevated without any prompt, even if "Run As Administrator" is not chosen by the user.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendUacSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter(Mandatory)]
        [switch]
        $DisableUAC
    )
    process
    {
        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-LUA-Settings', "offlineServicing")
        $UnattendBuilder.SetElementValue('EnableLUA', !$DisableUAC, $Component)
        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures various settings used during Windows setup in WinPE.
 
.DESCRIPTION
    Configures various settings used during Windows setup in WinPE.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER DisableFirewall
    Specifies whether Windows Firewall is enabled for Windows Preinstallation Environment (Windows PE).
    This setting does not apply to the Windows Firewall settings of the Windows installation.
 
.PARAMETER EnableNetwork
    Specifies whether network connection is enabled.
    This setting applies only to Windows Preinstallation Environment (Windows PE).
    In the standard Windows setup WinPE image, networking is disabled by default.
    For custom WinPE images, networking is enabled by default.
 
.PARAMETER LogDirectory
    Specifies where log files for WinPE will be saved.
 
.PARAMETER ShutdownAfterWinPE
    Specifies that WinPE should shutdown rather than reboot after finishing.
 
.PARAMETER UseConfigurationSet
    Specifies whether to use a configuration set for Windows Setup.
    A configuration set is a folder that contains additional device drivers, applications, or other binaries that you want to add to Windows during installation.
    You can create a configuration set in Windows System Image Manager.
 
.PARAMETER DisableDiskEncryptionProvisioning
    Specifies whether Windows activates encryption on blank drives that are capable of hardware-based encryption during installation.
 
.PARAMETER PagefilePath
    Specifies the path to use for the page file used in WinPE.
 
.PARAMETER PagefileSizeMB
    Specifies the max size of the page file used in WinPE.
 
.PARAMETER AcceptEula
    Specifies that you accept the Windows EULA of the image you are installing.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendWindowsSetupSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter()]
        [switch]
        $DisableFirewall,

        [Parameter()]
        [switch]
        $EnableNetwork,

        [Parameter()]
        [string]
        $LogDirectory,

        [Parameter()]
        [switch]
        $ShutdownAfterWinPE,

        [Parameter()]
        [switch]
        $UseConfigurationSet,

        [Parameter()]
        [switch]
        $DisableDiskEncryptionProvisioning,

        [Parameter()]
        [string]
        $PagefilePath,

        [Parameter()]
        [string]
        $PagefileSizeMB,

        [Parameter()]
        [switch]
        $AcceptEula
    )
    process
    {
        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-Setup', 'windowsPE')
        switch ($PSBoundParameters.Keys)
        {
            'DisableFirewall'
            {
                $UnattendBuilder.SetElementValue('EnableFirewall', !$DisableFirewall, $Component)
                continue
            }
            'EnableNetwork'
            {
                $UnattendBuilder.SetElementValue('EnableNetwork', $EnableNetwork, $Component)
                continue
            }
            'LogDirectory'
            {
                $UnattendBuilder.SetElementValue('LogPath', $LogDirectory, $Component)
                continue
            }
            'ShutdownAfterWinPE'
            {
                $Text = if ($ShutdownAfterWinPE)
                {
                    "Shutdown"
                }
                else
                {
                    "Restart"
                }
                $UnattendBuilder.SetElementValue('Restart', $Text, $Component)
                continue
            }
            'UseConfigurationSet'
            {
                $UnattendBuilder.SetElementValue('UseConfigurationSet', $UseConfigurationSet, $Component)
                continue
            }
            'DisableDiskEncryptionProvisioning'
            {
                $DiskConfig = $UnattendBuilder.GetOrCreateChildElement('DiskConfiguration', $Component)
                $UnattendBuilder.SetElementValue('DisableEncryptedDiskProvisioning', $DisableDiskEncryptionProvisioning, $DiskConfig)
                continue
            }
            'PagefilePath'
            {
                $PageFile = $UnattendBuilder.GetOrCreateChildElement('PageFile', $Component)
                $UnattendBuilder.SetElementValue('Path', $PagefilePath, $PageFile)
                continue
            }
            'PagefileSizeMB'
            {
                $PageFile = $UnattendBuilder.GetOrCreateChildElement('PageFile', $Component)
                $UnattendBuilder.SetElementValue('Size', $PagefileSizeMB, $PageFile)
                continue
            }
            'AcceptEula'
            {
                $UserData = $UnattendBuilder.GetOrCreateChildElement('UserData', $Component)
                $UnattendBuilder.SetElementValue('AcceptEula', $AcceptEula, $UserData)
                continue
            }
        }
        $UnattendBuilder
    }
}
<#
.SYNOPSIS
    Configures Windows RE settings.
 
.DESCRIPTION
    Configures Windows Recovery Environment settings.
 
.PARAMETER UnattendBuilder
    The UnattendBuilder object that this should be added to.
    Create one with the command: New-UnattendBuilder
 
.PARAMETER UninstallWindowsRE
    Uninstalls the recovery environment during OOBE.
    This can be used to save disk space (Typically, about 500MB) on systems where recovery options aren't needed.
 
.INPUTS
    [UnattendBuilder]
    Create one with the command: New-UnattendBuilder
 
.OUTPUTS
    [UnattendBuilder]
#>

function Set-UnattendWinReSetting
{
    [CmdletBinding(PositionalBinding = $false)]
    [OutputType([UnattendBuilder])]
    Param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [UnattendBuilder]
        $UnattendBuilder,

        [Parameter(Mandatory)]
        [switch]
        $UninstallWindowsRE
    )
    process
    {
        $Component = $UnattendBuilder.GetOrCreateComponent('Microsoft-Windows-WinRE-RecoveryAgent', 'oobeSystem')
        $UnattendBuilder.SetElementValue('UninstallWindowsRE', $UninstallWindowsRE, $Component)
        $UnattendBuilder
    }
}
$CultureCompleter = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = $wordToComplete.Trim(("'",'"'))
    foreach ($Culture in [cultureinfo]::GetCultures([System.Globalization.CultureTypes]::AllCultures))
    {
        if ($Culture.Name -ne "" -and $Culture.Name -like "$TrimmedWord*")
        {
            $CompletionText = $Culture.Name
            $ListItemText   = $Culture.Name
            $ResultType     = [System.Management.Automation.CompletionResultType]::ParameterValue
            $ToolTip        = $Culture.DisplayName
            [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $ToolTip)
        }
    }
}
Register-ArgumentCompleter -CommandName New-UnattendBuilder,Set-UnattendLanguageSetting -ParameterName UiLanguage      -ScriptBlock $CultureCompleter
Register-ArgumentCompleter -CommandName New-UnattendBuilder,Set-UnattendLanguageSetting -ParameterName SystemLocale    -ScriptBlock $CultureCompleter
Register-ArgumentCompleter -CommandName New-UnattendBuilder,Set-UnattendLanguageSetting -ParameterName InputLocale     -ScriptBlock $CultureCompleter
Register-ArgumentCompleter -CommandName Set-UnattendLanguageSetting                     -ParameterName SetupUiLanguage -ScriptBlock $CultureCompleter
Register-ArgumentCompleter -CommandName Set-UnattendLanguageSetting                     -ParameterName UserLocale -ScriptBlock $CultureCompleter
Register-ArgumentCompleter -CommandName Set-UnattendLanguageSetting                     -ParameterName UiLanguageFallback -ScriptBlock $CultureCompleter

Register-ArgumentCompleter -CommandName New-UnattendBuilder,Set-UnattendProductKey -ParameterName ProductKey -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = $wordToComplete.Trim(("'",'"'))
    $WindowsKeyTable = [ordered]@{
        Win10Home            = 'YTMG3-N6DKC-DKB77-7M9GH-8HVX7'
        Win10Pro             = 'VK7JG-NPHTM-C97JM-9MPGT-3V66T'
        Win10Edu             = 'YNMGQ-8RYV3-4PGQ3-C8XTP-7CFBY'
        Win10Enterprise      = 'XGVPP-NMH47-7TTHJ-W3FW7-8HV2C'
        Server2016Standard   = 'WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY'
        Server2016Datacenter = 'CB7KF-BWN84-R7R2Y-793K2-8XDDG'
        Server2019Standard   = 'N69G4-B89J2-4G8F4-WWYCC-J464C'
        Server2019Datacenter = 'WMDGN-G9PQG-XVVXX-R3X43-63DFG'
        Server2022Standard   = 'VDYBN-27WPP-V4HQT-9VMD4-VMK7H'
        Server2022Datacenter = 'WX4NM-KYWYW-QJJR4-XV3QB-6VM33'
    }
    foreach ($Key in $WindowsKeyTable.Keys)
    {
        if ($Key -like "$TrimmedWord*")
        {
            $CompletionText = $WindowsKeyTable[$Key]
            $ListItemText   = $Key
            $ResultType     = [System.Management.Automation.CompletionResultType]::ParameterValue
            $ToolTip        = $Key
            [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $ToolTip)
        }
    }
}

$FwGroupCompleter = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = $wordToComplete.Trim(("'",'"'))
    $AllGroups = @'
DisplayGroup;Group
COM+ Network Access;@%systemroot%\system32\firewallapi.dll,-3400
COM+ Remote Administration;@%systemroot%\system32\firewallapi.dll,-3405
Core Networking;@FirewallAPI.dll,-25000
Core Networking Diagnostics;@FirewallAPI.dll,-27000
DHCP Server;@FirewallAPI.dll,-50209
DHCP Server Management;@FirewallAPI.dll,-50213
DIAL protocol server;@FirewallAPI.dll,-37101
Distributed Transaction Coordinator;@FirewallAPI.dll,-33502
DNS Service;@firewallapi.dll,-53012
File and Printer Sharing;@FirewallAPI.dll,-28502
File and Printer Sharing over QUIC;@FirewallAPI.dll,-28652
File and Printer Sharing over SMBDirect;@FirewallAPI.dll,-28602
File Server Remote Management;@fssmres.dll,-100
Hyper-V;@%systemroot%\system32\vmms.exe,-210
Hyper-V Management Clients;@FirewallAPI.dll,-60201
Hyper-V Replica HTTP;@%systemroot%\system32\vmms.exe,-251
Hyper-V Replica HTTPS;@%systemroot%\system32\vmms.exe,-253
iSCSI Service;@FirewallAPI.dll,-29002
Key Management Service;@FirewallAPI.dll,-28002
mDNS;@%SystemRoot%\system32\firewallapi.dll,-37302
Microsoft Media Foundation Network Source;@FirewallAPI.dll,-54001
Netlogon Service;@firewallapi.dll,-37681
Network Discovery;@FirewallAPI.dll,-32752
Performance Logs and Alerts;@FirewallAPI.dll,-34752
Remote Desktop;@FirewallAPI.dll,-28752
Remote Desktop (WebSocket);@FirewallAPI.dll,-28782
Remote Event Log Management;@FirewallAPI.dll,-29252
Remote Event Monitor;@FirewallAPI.dll,-36801
Remote Scheduled Tasks Management;@FirewallAPI.dll,-33252
Remote Service Management;@FirewallAPI.dll,-29502
Remote Shutdown;@firewallapi.dll,-36751
Remote Volume Management;@FirewallAPI.dll,-34501
Routing and Remote Access;@FirewallAPI.dll,-33752
Secure Socket Tunneling Protocol;@sstpsvc.dll,-35001
SNMP Trap;@firewallapi.dll,-50323
Windows Defender Firewall Remote Management;@FirewallAPI.dll,-30002
Windows Deployment Services;@firewallapi.dll,-38201
Windows Device Management;@FirewallAPI.dll,-37502
Windows Management Instrumentation (WMI);@FirewallAPI.dll,-34251
Windows Remote Management;@FirewallAPI.dll,-30267
Windows Remote Management (Compatibility);@FirewallAPI.dll,-30252
'@
 | ConvertFrom-Csv -Delimiter ';'
    foreach ($Group in $AllGroups)
    {
        if ($Group.DisplayGroup -like "$TrimmedWord*")
        {
            $CompletionText = "'$($Group.Group)'"
            $ListItemText   = $Group.DisplayGroup
            $ResultType     = [System.Management.Automation.CompletionResultType]::ParameterValue
            $ToolTip        = $Group.DisplayGroup
            [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $ToolTip)
        }
    }
}
Register-ArgumentCompleter -CommandName Set-UnattendFirewallSetting -ParameterName EnabledFirewallGroups -ScriptBlock $FwGroupCompleter
Register-ArgumentCompleter -CommandName Set-UnattendFirewallSetting -ParameterName DisabledFirewallGroups -ScriptBlock $FwGroupCompleter