GuardedFabricTools.psm1
function New-ShieldingDataAnswerFile { <# .SYNOPSIS Creates a Windows unattended installatoin answer file for use in Shielding Data Files. .DESCRIPTION Creates a Windows unattended installation answer file for use in Shielding Data Files. The answer file assumes the guest OS is Windows Server 2016 (Server with Desktop Experience or Server Core edition) and that the guarded fabric is managed by System Center Virtual Machine Manager 2016. .PARAMETER AdminPassword The password assigned to the default local administrator account. .PARAMETER BackupBitLockerKeyProtector Schedules a task to back up the BitLocker recovery key to Active Directory after deployment. .PARAMETER DomainJoin The name of the domain to which the VM will join. When omitted, the VM will belong to a workgroup. .PARAMETER DomainJoinCredential The credentials used to join the VM to the domain. .PARAMETER DscPullServerUrl The URL of your DSC pull server used to configure the Local Configuration Manager. A configuration ID is also required when using this setting. .PARAMETER DscConfigurationID The configuration ID to request from the DSC pull server. .PARAMETER Language The desired language and locale for the operating system. For example en-us, fr-ca, zh-hk, etc. .PARAMETER Path The location where the answer file will be saved. .PARAMETER ProductKey Configures whether the answer file contains a placeholder for a product key. Use "None" if your OS is volume licensed or an evaluation edition. Use "UserSupplied" when a product key is required. When set to "UserSupplied", Virtual Machine Manager is expected to provide the product key. .PARAMETER RDPCertificateFilePath Path to a certificate file containing the public and private keys used to configure Remote Desktop Services on the VM. .PARAMETER RDPCertificatePassword The password for the RDP certificate file. .PARAMETER SetupScriptFilePath Path to a PowerShell script (.ps1) containing custom configuration information to run during OS intallation. .PARAMETER StaticIP Adds configuration information to the answer file to set IP configuration information using VMM Static IP Pools. You will need to ensure your Static IP Pool shares an IPv4 address, MAC address, gateway, DNS server, and prefix. .EXAMPLE New-ShieldingDataAnswerFile -AdminPassword $adminpwd -DomainJoin redmond.corp.microsoft.com -DomainJoinCredential $mycreds -RDPCertificateFilePath C:\RDPCert.pfx -RDPCertificatePassword $rdppwd -ProductKey UserSupplied -Path C:\unattend.xml .EXAMPLE New-ShieldingDataAnswerFile -AdminPassword $adminpwd -DomainJoin redmond.corp.microsoft.com -DomainJoinCredential $mycreds -ProductKey UserSupplied -Path C:\unattend.xml .EXAMPLE New-ShieldingDataAnswerFile -AdminPassword $adminpwd -ProductKey UserSupplied -Path C:\unattend.xml .EXAMPLE New-ShieldingDataAnswerFile -AdminPassword $adminpwd -StartupScriptFilename HelloWorld.ps1 .EXAMPLE New-ShieldingDataAnswerFile -AdminPassword $adminpwd -StaticIP .EXAMPLE New-ShieldingDataAnswerFile -AdminPassword $adminpwd -DSCPullServer pullserver.com .EXAMPLE New-ShieldingDataAnswerFile -AdminPassword $adminpwd -Language ar-ma #> Param ( [Parameter (Mandatory=$true)] [SecureString]$AdminPassword, [Parameter (ParameterSetName='DomainJoin', Mandatory=$true)] [Parameter (ParameterSetName='DomainJoinNoRDP', Mandatory=$true)] [string]$DomainJoin, [Parameter (ParameterSetName='DomainJoin', Mandatory=$true)] [Parameter (ParameterSetName='DomainJoinNoRDP', Mandatory=$true)] [PSCredential]$DomainJoinCredential, [Parameter (ParameterSetName='WorkgroupRDP', Mandatory=$true)] [Parameter (ParameterSetName='DomainJoin', Mandatory=$true)] [string]$RDPCertificateFilePath, [Parameter (ParameterSetName='WorkgroupRDP', Mandatory=$true)] [Parameter (ParameterSetName='DomainJoin', Mandatory=$true)] [SecureString]$RDPCertificatePassword, [Parameter (ParameterSetName='DomainJoin', Mandatory=$false)] [Parameter (ParameterSetName='DomainJoinNoRDP', Mandatory=$false)] [Parameter (ParameterSetName='WorkgroupRDP', Mandatory=$false)] [Parameter (ParameterSetName='WorkgroupNoRDP', Mandatory=$false)] [string]$SetupScriptFilePath, [string]$DSCPullServerURL, [string]$DSCConfigurationID, [Parameter()] [ValidateSet("None","UserSupplied")] [string] $ProductKey = "None", [Switch]$StaticIP, [Switch]$BackupBitLockerKeyProtector, [string]$Path = ".\unattend.xml", [string]$Language = "en-US" ) Set-StrictMode -Version 3.0 ### Helper function ### Function SecureStringToString { Param ($MySecureString) Try { $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($MySecureString) $MyString = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) return $MyString } Finally { [System.Runtime.InteropServices.Marshal]::FreeBSTR($BSTR) } } $AdminStringPassword = SecureStringToString $AdminPassword ### Computer name ### $content = '<?xml version="1.0" encoding="utf-8"?> <unattend xmlns="urn:schemas-microsoft-com:unattend"> <servicing></servicing> <settings pass="specialize"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ComputerName>@ComputerName@</ComputerName>' ### Product Key ### if ($ProductKey -eq 'UserSupplied') { $content += ' <ProductKey>@ProductKey@</ProductKey>' } $content += ' </component>' $content += ' <!-- This is where all the RunSynchronousCommand lies. You can add extra commands here --> <component name="Microsoft-Windows-Deployment" 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"> <RunSynchronous>' $order = 0 ### Import RDP file ### if ($PSCmdlet.ParameterSetName -eq "DomainJoin" -Or $PSCmdlet.ParameterSetName -eq "WorkgroupRDP") { $RDPStringPassword = SecureStringToString $RDPCertificatePassword $RDPCertificateFilename = Split-Path -Path $RDPCertificateFilePath -Leaf -Resolve Get-Item ($RDPCertificateFilePath) | ForEach-Object { $certobject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $certobject.Import($_.FullName, $RDPCertificatePassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet) $certThumb = $certobject.Thumbprint } $content += ' <!-- You can change the RDP Certificate password here --> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 1)+'</Order> <!-- Update with your certificate password --> <Path>certutil -p "'+[System.Security.SecurityElement]::Escape($RDPStringPassword)+'" -importpfx %SYSTEMDRIVE%\temp\'+[System.Security.SecurityElement]::Escape($RDPCertificateFilename)+'</Path> <WillReboot>OnRequest</WillReboot> <Description>Import certificate</Description> </RunSynchronousCommand>' $order += 1 } ### Creating the SetupComplete.cmd ### $content += ' <!-- We are creating our own SetupComplete.cmd to run scripts during the setup process. Add commands to this cmd file if you have custom setup actions to do --> <RunSynchronousCommand wcm:action="add"> <Description>If there is one, copy original setupcomplete.cmd to a unique file</Description> <Order>'+($order + 1)+'</Order> <Path>cmd /C if exist {%WINDIR%\Setup\Scripts\SetupComplete.cmd} (copy %WINDIR%\Setup\Scripts\SetupComplete.cmd %WINDIR%\Setup\Scripts\SetupComplete_Original.cmd /y)</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 2)+'</Order> <Description>mkdir Scripts since Windows looks for SetupComplete.cmd in that dir. If the dir exists, it should be fine.</Description> <Path>cmd.exe /C mkdir %WINDIR%\Setup\Scripts</Path> </RunSynchronousCommand>' $order += 2 ### Delete the RDPCert file ### if ($PSCmdlet.ParameterSetName -eq "DomainJoin" -Or $PSCmdlet.ParameterSetName -eq "WorkgroupRDP") { $content += ' <!-- We will delete the RDP certificate and the answer files to hide sensitive data --> <RunSynchronousCommand wcm:action="add"> <Description>Delete the RDPCert file</Description> <Order>'+($order + 1)+'</Order> <Path>cmd /C echo del %SYSDRIVE%\temp\'+[System.Security.SecurityElement]::Escape($RDPCertificateFilename)+' >> %WINDIR%\Setup\Scripts\SetupComplete.cmd</Path> </RunSynchronousCommand>' $order += 1 } ### Remove all unattend.xml files ### $content += ' <RunSynchronousCommand wcm:action="add"> <Description>Delete the answer file from C:</Description> <Order>'+($order + 1)+'</Order> <Path>cmd /C echo del %SYSDRIVE%\unattend.xml >> %WINDIR%\Setup\Scripts\SetupComplete.cmd</Path> </RunSynchronousCommand>' $order += 1 $content += ' <RunSynchronousCommand wcm:action="add"> <Description>Delete the answer file from C:\Windows\Panther</Description> <Order>'+($order + 1)+'</Order> <Path>cmd /C echo del %WINDIR%\Panther\unattend.xml >> %WINDIR%\Setup\Scripts\SetupComplete.cmd</Path> </RunSynchronousCommand>' $order += 1 ### RDP Thumbprint ### if ($PSCmdlet.ParameterSetName -eq "DomainJoin" -Or $PSCmdlet.ParameterSetName -eq "WorkgroupRDP") { $content += ' <!-- This is to tell the computer to use the RDP certificate you have just imported rather than using its own --> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 1)+'</Order> <Description>Put certificate configuration command in SetupComplete.cmd</Description> <Path>cmd /C echo wmic /namespace:\\root\cimv2\TerminalServices PATH Win32_TSGeneralSetting Set SSLCertificateSHA1Hash="'+[System.Security.SecurityElement]::Escape($certThumb)+'" >> %WINDIR%\Setup\Scripts\SetupComplete.cmd</Path> </RunSynchronousCommand>' $order += 1 } ### DSC pull server ### if ($DSCPullServerURL) { $content += ' <!-- You can modify the DSC pull server URL and the DSC configuration ID here --> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 1)+'</Order> <Description>Write the Configuration file for the DSC Pull Server Part 1</Description>' if ($DSCConfigurationID) { $content += ' <Path>cmd /C echo [DscLocalConfigurationManager()]configuration meta{Node localhost{Settings{ConfigurationMode = "ApplyAndAutocorrect"; ConfigurationID = "'+[System.Security.SecurityElement]::Escape($DSCConfigurationID)+'"; >> %WINDIR%\Setup\Scripts\PullServerConfig.ps1</Path>' } else { $content += ' <Path>cmd /C echo [DscLocalConfigurationManager()]configuration meta{Node localhost{Settings{ConfigurationMode = "ApplyAndAutocorrect"; >> %WINDIR%\Setup\Scripts\PullServerConfig.ps1</Path>' } $content += ' </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 2)+'</Order> <Description>Write the Configuration file for the DSC Pull Server Part 2</Description> <Path>cmd /C echo RefreshMode = "Pull"}ConfigurationRepositoryWeb PullServer{ServerURL = "'+[System.Security.SecurityElement]::Escape($DSCPullServerURL)+'"}}} >> %WINDIR%\Setup\Scripts\PullServerConfig.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 3)+'</Order> <Description>Make the meta directory to put the localhost.meta.mof</Description> <Path>cmd /C echo mkdir %WINDIR%\Setup\Scripts\meta >> %WINDIR%\Setup\Scripts\PullServerConfig.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 4)+'</Order> <Description>Write the PullServerConfig.ps1 script - Initializing the script</Description> <Path>cmd /C echo meta -output %WINDIR%\Setup\Scripts\meta >> %WINDIR%\Setup\Scripts\PullServerConfig.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 5)+'</Order> <Description>Write the PullServerConfig.ps1 script - Set the LCM</Description> <Path>cmd /C echo Set-DscLocalConfigurationManager localhost -Path %WINDIR%\Setup\Scripts\meta >> %WINDIR%\Setup\Scripts\PullServerConfig.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 6)+'</Order> <Description>Run the PullServerConfig.ps1 script</Description> <Path>cmd /C echo powershell %WINDIR%\Setup\Scripts\PullServerConfig.ps1 >> %WINDIR%\Setup\Scripts\SetupComplete.cmd</Path> </RunSynchronousCommand>' $order += 6 } ### Setup Script ### if ($SetupScriptFilePath){ $SetupScriptFile = New-Object System.IO.FileInfo($SetupScriptFilePath) $SetupScriptFilePath = $SetupScriptFile.BaseName + $SetupScriptFile.Extension $content += ' <!-- All files attached to the PDK will end up in C:\temp\, hence this will run the setup script you have included --> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 1)+'</Order> <Description>Run the provided Powershell Setup Script</Description> <Path>cmd /C echo powershell %SYSTEMDRIVE%\temp\'+[System.Security.SecurityElement]::Escape($SetupScriptFilePath)+ ' >> %WINDIR%\Setup\Scripts\SetupComplete.cmd</Path> </RunSynchronousCommand>' $order += 1 } ### Script for BitLocker recovery password ### if (($PSCmdlet.ParameterSetName -eq "DomainJoin" -Or $PSCmdlet.ParameterSetName -eq "DomainJoinNoRDP") -And $BackupBitLockerKeyProtector) { ## Create a script that creates a new task for the Task Scheduler ## This will be used to run the BitLockerRecoveryPassword Script ## as it needs to be run after SetupComplete.exe and during startup $content += ' <!-- This is writing a CreateScheduledTask PowerShell Script that creates a startup task --> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 1)+'</Order> <Description>Create the CreateScheduledTask Script Part 1/6</Description> <Path>cmd /C echo Start-Transcript -Path %WINDIR%\Setup\Scripts\CreateScheduledTaskOutput.txt >> %WINDIR%\Setup\Scripts\CreateScheduledTask.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 2)+'</Order> <Description>Create the CreateScheduledTask Script Part 1/6</Description> <Path>cmd /C echo $action = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument ''-NoProfile -WindowStyle Hidden -File %WINDIR%\Setup\Scripts\BitLockerRecoveryPassword.ps1'' >> %WINDIR%\Setup\Scripts\CreateScheduledTask.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 3)+'</Order> <Description>Create the CreateScheduledTask Script Part 2/6</Description> <Path>cmd /C echo $trigger = New-ScheduledTaskTrigger -AtStartup >> %WINDIR%\Setup\Scripts\CreateScheduledTask.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 4)+'</Order> <Description>Create the CreateScheduledTask Script Part 3/6</Description> <Path>cmd /C echo $Stset = New-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries >> %WINDIR%\Setup\Scripts\CreateScheduledTask.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 5)+'</Order> <Description>Create the CreateScheduledTask Script Part 4/6</Description> <Path>cmd /C echo Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "BackupBitLockerRecoveryPassword" `>> %WINDIR%\Setup\Scripts\CreateScheduledTask.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 6)+'</Order> <Description>Create the CreateScheduledTask Script Part 5/6</Description> <Path>cmd /C echo -Description "Backup BitLocker Recovery Password to AD" -User "@ComputerName@\Administrator" -Password "'+[System.Security.SecurityElement]::Escape($AdminStringPassword)+'" -Settings $Stset >> %WINDIR%\Setup\Scripts\CreateScheduledTask.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 7)+'</Order> <Description>Create the CreateScheduledTask Script Part 6/6</Description> <Path>cmd /C echo rm %WINDIR%\Setup\Scripts\CreateScheduledTask.ps1 >> %WINDIR%\Setup\Scripts\CreateScheduledTask.ps1</Path> </RunSynchronousCommand>' $order += 7 $content += ' <!-- This is writing a Script that runs to create a KeyProtector that uses a recovery password, and it will back up to the Active Directory --> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 1)+'</Order> <Description>Create the BitLockerRecoveryPassword Script Part 1/6</Description> <Path>cmd /C echo Start-Transcript -Path %WINDIR%\Setup\Scripts\BitLockerRecoveryScriptOutput.txt >> %WINDIR%\Setup\Scripts\BitLockerRecoveryPassword.ps1</Path> </RunSynchronousCommand>' $content += ' <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 2)+'</Order> <Description>Create the BitLockerRecoveryPassword Script Part 2/6</Description> <Path>cmd /C echo gpupdate /force >> %WINDIR%\Setup\Scripts\BitLockerRecoveryPassword.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 3)+'</Order> <Description>Create the BitLockerRecoveryPassword Script Part 3/6</Description>' $content += @' <Path>cmd /C echo $b = Add-BitLockerKeyProtector -MountPoint %SYSTEMDRIVE% -RecoveryPasswordProtector; $kp = $b.KeyProtector ^| ?{ $_.KeyProtectorType -eq 'RecoveryPassword' }; $kpId = $kp.KeyProtectorId >> %WINDIR%\Setup\Scripts\BitLockerRecoveryPassword.ps1</Path> '@ $content += ' </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 4)+'</Order> <Description>Create the BitLockerRecoveryPassword Script Part 4/6</Description> <Path>cmd /C echo Backup-BitLockerKeyProtector -MountPoint %SYSTEMDRIVE% -KeyProtectorId $kpId >> %WINDIR%\Setup\Scripts\BitLockerRecoveryPassword.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 5)+'</Order> <Description>Create the BitLockerRecoveryPassword Script Part 5/6</Description> <Path>cmd /C echo Invoke-Expression "schtasks.exe /delete /s @ComputerName@ /tn BackupBitLockerRecoveryPassword /F" >> %WINDIR%\Setup\Scripts\BitLockerRecoveryPassword.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 6)+'</Order> <Description>Create the BitLockerRecoveryPassword Script Part 6/6</Description> <Path>cmd /C echo Stop-Transcript >> %WINDIR%\Setup\Scripts\BitLockerRecoveryPassword.ps1</Path> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 7)+'</Order> <Description>Run the CreateScheduledTask Script</Description> <Path>cmd /C echo powershell %WINDIR%\Setup\Scripts\CreateScheduledTask.ps1 >> %WINDIR%\Setup\Scripts\SetupComplete.cmd</Path> </RunSynchronousCommand>' $order += 7 } ### End script for recovery password ### Shutdown the VM ### $content += ' <!-- This shuts down the VM --> <RunSynchronousCommand wcm:action="add"> <Order>'+($order + 1)+'</Order> <Description>Put shutdown VM in SetupComplete.cmd</Description> <Path>cmd /C echo shutdown /s /f >> %WINDIR%\Setup\Scripts\SetupComplete.cmd</Path> <WillReboot>OnRequest</WillReboot> </RunSynchronousCommand>' $order += 1 $content += ' </RunSynchronous> </component>' ### StaticIP if ($StaticIP) { $content += ' <!-- Since you are using StaticIP, you must specify all these parameters --> <component name="Microsoft-Windows-TCPIP" 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"> <Interfaces> <Interface wcm:action="add"> <Ipv4Settings> <DhcpEnabled>false</DhcpEnabled> </Ipv4Settings> <UnicastIpAddresses> <IpAddress wcm:action="add" wcm:keyValue="1">@IP4Addr-1@</IpAddress> </UnicastIpAddresses> <Identifier>@MACAddr-1@</Identifier> <Routes> <Route wcm:action="add"> <Identifier>1</Identifier> <Prefix>@Prefix-1-1@</Prefix> <NextHopAddress>@NextHop-1-1@</NextHopAddress> </Route> </Routes> </Interface> </Interfaces> </component> <component name="Microsoft-Windows-DNS-Client" 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"> <Interfaces> <Interface wcm:action="add"> <DNSServerSearchOrder> <IpAddress wcm:action="add" wcm:keyValue="1">@DnsAddr-1-1@</IpAddress> </DNSServerSearchOrder> <Identifier>@MACAddr-1@</Identifier> </Interface> </Interfaces> </component>' } ### Join Domain if ($PSCmdlet.ParameterSetName -eq "DomainJoin" -Or $PSCmdlet.ParameterSetName -eq "DomainJoinNoRDP") { $DomainStringPassword = $DomainJoinCredential.GetNetworkCredential().Password $DomainStringUsername = $DomainJoinCredential.GetNetworkCredential().Username $DomainStringDomain = $DomainJoinCredential.GetNetworkCredential().Domain $DomainStringPassword = [System.Security.SecurityElement]::Escape($DomainStringPassword) $DomainStringUsername = [System.Security.SecurityElement]::Escape($DomainStringUsername) $DomainStringDomain = [System.Security.SecurityElement]::Escape($DomainStringDomain) $content += ' <!-- You must provide credentials to join a domain --> <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Identification> <Credentials>' if ($DomainStringDomain) { $content += " <Domain>$DomainStringDomain</Domain>" } $content += " <Username>$DomainStringUsername</Username> <Password>$DomainStringPassword</Password> </Credentials> <JoinDomain>$DomainJoin</JoinDomain> </Identification> </component>" } ### Allow RDP and Configure Admin Password $content += ' <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" 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"> <fDenyTSConnections>false</fDenyTSConnections> </component> <component name="Networking-MPSSVC-Svc" 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"> <FirewallGroups> <!-- Allow RDP connections through the firewall --> <FirewallGroup wcm:action="add" wcm:keyValue="RDGroup"> <Active>true</Active> <Group>@FirewallAPI.dll,-28752</Group> <Profile>all</Profile> </FirewallGroup> </FirewallGroups> </component> <component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" 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"> <UserAuthentication>0</UserAuthentication> </component> </settings> <settings pass="oobeSystem"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <OOBE> <HideEULAPage>true</HideEULAPage> <SkipUserOOBE>true</SkipUserOOBE> </OOBE> <!-- You can add extra user accounts here, in addition to the Administrator --> <UserAccounts> <AdministratorPassword> <Value>'+[System.Security.SecurityElement]::Escape($AdminStringPassword)+'</Value> <PlainText>true</PlainText> </AdministratorPassword>' ### Timezone and Languages $content += ' </UserAccounts> <TimeZone>@TimeZone@</TimeZone> </component> <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <UserLocale>'+$Language+'</UserLocale> <SystemLocale>'+$Language+'</SystemLocale> <UILanguage>'+$Language+'</UILanguage> <InputLocale>'+$Language+'</InputLocale> </component> </settings> </unattend>' ### Save to xml file $xmldoc = [xml]$content $folder = Split-Path $Path | Resolve-Path $file = Split-Path $Path -Leaf $fullPath = Join-Path $folder.Path $file $xmldoc.Save($fullpath) if ($StaticIP) { Write-Warning -Message @' You have enabled StaticIP. For this answer file, we expect you to configure the VMM to contain only a single NIC and to have your Static IP Pool setup correctly. You should have configured on VMM to have one DNS server address and IPv4 static IP address. You can modify these values from the answer file if you want to do it manually: @IP4Addr-1@ @MACAddr-1@ @Prefix-1-1@ @NextHop-1-1@ @DnsAddr-1-1@ '@ } Get-Item $fullPath } |