Advertisement
Guest User

Untitled

a guest
May 21st, 2016
345
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. Set-StrictMode -version 3
  2. $ErrorActionPreference = "Stop"
  3.  
  4. $Script:LogFile = Join-Path -Path $Env:TEMP -ChildPath "NanoServerImageGenerator.log"
  5. $Script:DismLogFile = Join-Path -Path $Env:TEMP -ChildPath "NanoServerImageGenerator (DISM).log"
  6.  
  7. $Script:VSDebuggerBinariesPath = Join-Path -Path ${Env:CommonProgramFiles(x86)} -ChildPath "Microsoft Shared\Phone Tools\14.0\Debugger\target\x64"
  8. $Script:VSRedistOnecorePath = Join-Path -Path ${Env:ProgramFiles(x86)} -ChildPath "Microsoft Visual Studio 14.0\VC\redist\onecore"
  9. $Script:VSCRTReleaseBinariesPath = Join-Path -Path $Script:VSRedistOnecorePath -ChildPath "x64\Microsoft.VC140.CRT"
  10. $Script:VSCRTDebugBinariesPath = Join-Path -Path $Script:VSRedistOnecorePath -ChildPath "debug_nonredist\x64\Microsoft.VC140.DebugCRT"
  11. $Script:VSUCRTBinariesPath = Join-Path -Path ${Env:ProgramFiles(x86)} -ChildPath "Windows Kits\10\bin\x64\ucrt"
  12.  
  13. ### -----------------------------------
  14. ### Constants
  15. ### -----------------------------------
  16.  
  17. $IMAGE_NAME = "NanoServer.wim"
  18. $IMAGE_CONVERTER = "Convert-WindowsImage.ps1"
  19.  
  20. $KERNEL_DEBUG_KEY_FILE = "KernelDebugKey.txt"
  21.  
  22. $BCD_TEMPLATE_PCAT_ENTRY = "``{a1943bbc-ea85-487c-97c7-c9ede908a38a``}"
  23. $BCD_TEMPLATE_EFI_ENTRY = "``{b012b84d-c47c-4ed5-b722-c0c42163e569``}"
  24.  
  25. $LEVEL_WARNING = "WARNING"
  26. $LEVEL_VERBOSE = "VERBOSE"
  27. $LEVEL_OUTPUT = "OUTPUT"
  28. $LEVEL_NONE = "NONE"
  29.  
  30. $DT_HOST = "Host"
  31. $DT_GUEST = "Guest"
  32.  
  33. $Script:Editions = @{
  34.     "Standard" = 1
  35.     "Datacenter" = 2
  36. }
  37.  
  38. $PACK_STORAGE = "Microsoft-NanoServer-Storage-Package"
  39. $PACK_COMPUTE = "Microsoft-NanoServer-Compute-Package"
  40. $PACK_DEFENDER = "Microsoft-NanoServer-Defender-Package"
  41. $PACK_CLUSTERING = "Microsoft-NanoServer-FailoverCluster-Package"
  42. $PACK_OEM_DRIVERS = "Microsoft-NanoServer-OEM-Drivers-Package"
  43. $PACK_GUEST_DRIVERS = "Microsoft-NanoServer-Guest-Package"
  44. $PACK_CONTAINERS = "Microsoft-NanoServer-Containers-Package"
  45. $PACK_RAMDISK = "Microsoft-NanoServer-BootFromWim-Package"
  46. $PACK_HOST = "Microsoft-NanoServer-Host-Package"
  47.  
  48. ### -----------------------------------
  49. ### Strings
  50. ### -----------------------------------
  51.  
  52. Data Strings
  53. {
  54. # culture="en-US"
  55. ConvertFrom-StringData @'
  56.    # "Administrator" refers to the Windows user account.
  57.    ERR_USER_MUST_BE_ADMINISTRATOR = This script must be run as Administrator.
  58.    
  59.    # The strings after the dashes are not translatable.
  60.    ERR_INCLUDE_DOMAIN_NAME_OR_DOMAIN_BLOB_PATH = Include either -DomainName or -DomainBlobPath, not both.
  61.    ERR_INCLUDE_COMPUTER_NAME_OR_DOMAIN_BLOB_PATH = Include either -ComputerName or -DomainBlobPath, not both.
  62.    ERR_DOMAIN_NAME_NO_COMPUTER_NAME = -DomainName was included, but not -ComputerName.
  63.    ERR_EMS_PORT_WITH_NO_EMS = -EMSPort was included, but not -EnableEMS.
  64.    ERR_DEBUG_AND_EMS_PORTS_EQUAL = Both the kernel debugging port and the EMS port cannot be the same.
  65.    ERR_REUSE_NODE_WITHOUT_JOIN = -ReuseDomainJoin was specified without -DomainName nor -DomainBlobPath.
  66.    ERR_KDNET_KEY_NOT_PRODUCED = Unable to create KDNET key automatically, please provide one.
  67.    ERR_NO_INTERFACE_SPECIFIED = When changing IP configuration, -InterfaceNameOrIndex needs to be specified.
  68.    ERR_RAMDISK_FOR_WIM_ONLY = Only WIM images are supported for RAMDisk boot.
  69.    ERR_INCLUDE_OEM_WITH_DT_GUEST = Do not include -OEMDrivers ('{0}') when -DeploymentType is {1}.
  70.    ERR_IMAGE_WAS_NOT_PRODUCED = The requested image could not be created. Please consult the command output for additional information.
  71.    ERR_REUSE_DOMAIN_NAME = The domain name might have been already used. You might need to rerun with -ReuseDomainNode.
  72.    ERR_MSVS_REQUIRED = You have to install Microsoft Visual Studio 2015 Update 1 or higher in order to use -Development. You will also need to enable 'Common Tools for Visual C++' and 'Tools and Windows 10 SDK'.
  73.    ERR_PACKAGE_NOT_APPLICABLE = Package '{0}' is not applicable to the selected edition.
  74.    ERR_PASSWORD_EMPTY = Administrator password cannot be empty.
  75.  
  76.    # {0} is a number.
  77.    ERR_EXTERNAL_CMD = Failed with {0}.
  78.  
  79.    # For the next block of messages, the strings between single quotes are not translatable.
  80.    ERR_DIRECTORY_DOES_NOT_EXIST_IN_MEDIA_DIRECTORY = The '{0}' directory does not exist in the specified media path.
  81.    # {2} is a path.
  82.    ERR_DIRECTORY_DOES_NOT_EXIST_IN_DIRECTORY = The '{0}' directory does not exist in the '{1}' directory ('{2}').
  83.    ERR_BASE_DIRECTORY_DOES_NOT_EXIST = The specified base directory does not exist.
  84.    ERR_DIRECTORY_DOES_NOT_EXIST_IN_BASE_DIRECTORY = The '{0}' directory does not exist in the specified base directory.
  85.    ERR_IMAGE_DOES_NOT_EXIST = The '{0}' image does not exist in the 'NanoServer' directory.
  86.    ERR_IMAGE_DOES_NOT_EXIST_IN_BASE_DIRECTORY = The '{0}' image does not exist in the specified base directory.
  87.    ERR_IMAGE_CONVERTER_SCRIPT_DOES_NOT_EXIST = The image converter script ('{0}') does not exist in the directory where this script is located.
  88.    ERR_PACKAGE_DOES_NOT_EXIST = Package '{0}' does not exist.
  89.    ERR_LANGUAGE_PACKAGE_DOES_NOT_EXIST = Language package '{0}' does not exist.
  90.    ERR_ONE_OR_MORE_PACKAGES_DO_NOT_EXIST = One or more packages do not exist.
  91.    ERR_DOMAIN_BLOB_DOES_NOT_EXIST = The specified domain blob does not exist.
  92.    ERR_DRIVERS_DIRECTORY_DOES_NOT_EXIST = The specified drivers directory does not exist ('{0}').
  93.    ERR_SPECIFIED_IMAGE_DOES_NOT_EXIST = The specified VHD(X)/WIM image does not exist.
  94.    ERR_ONLY_ONE_PATH_PERMITTED = MediaPath and BasePath specified, but only one path expected.
  95.    ERR_COPY_FILES_DOES_NOT_EXIST = CopyFiles specified but does not exist.
  96.    ERR_MEDIA_PATH_WAS_NOT_SPECIFIED = MediaPath has not been specified. You need to run New-NanoServerImage with -MediaPath first.
  97.    ERR_UNATTEND_TEMPLATE_DOES_NOT_EXIST = The specified Unattend file does not exist.
  98.    
  99.    # New-NanoImage cannot be translated.
  100.    LOG_HEADER = {0} Cmdlet Started
  101.  
  102.    # {0} is a file path.
  103.    MSG_DONE = Done. The log is at: {0}
  104.    # {0} is a file path.
  105.    MSG_TERMINATING_DUE_TO_ERROR = Terminating due to an error. See log file at: {0}
  106.  
  107.    MSG_COMPUTING_PATHS = Computing paths...
  108.    MSG_CHECKING_PATHS = Checking paths...
  109.    MSG_CREATING_PATHS = Creating paths...
  110.  
  111.    MSG_COPYING_FILES = Copying files...
  112.    MSG_SKIPPING_FILE_COPY = Skipping file copy.
  113.    
  114.    MSG_CONVERTING_IMAGE = Converting image...
  115.  
  116.    MSG_MOUNTING_IMAGE = Mounting image...
  117.  
  118.    MSG_COPYING_FILES_TO_IMAGE = Copying files to the image...
  119.    MSG_SKIPPING_COPYING_FILES_TO_IMAGE = Skipping copying of files to the image.
  120.    
  121.    MSG_ADDING_DEBUGGER = Adding debugger...
  122.    
  123.    MSG_ADDING_PACKAGES = Adding packages...
  124.    # {0} is a file name.
  125.    MSG_ADDING_PACKAGE = Adding package '{0}'...
  126.    # {0} is a file name.
  127.    MSG_ADDING_LANGUAGE_PACKAGE = Adding language package for '{0}'...
  128.    MSG_SKIPPING_PACKAGE_ADDITION = Skipping package addition.
  129.  
  130.    MSG_ADDING_DRIVERS = Adding drivers...
  131.    MSG_SKIPPING_DRIVER_ADDITION = Skipping driver addition.
  132.  
  133.    # The file name is not translatable.
  134.    MSG_ADDING_UNATTEND = Adding Unattend.xml...
  135.    MSG_COLLECTING_DOMAIN_BLOB = Collecting domain provisioning blob...
  136.    MSG_JOINING_DOMAIN = Joining domain...
  137.    
  138.    MSG_SETUPCOMPLETE_CHANGES_FOR_BOOTED_MEDIA = If the target image has been already booted, some of the requested changes will not be applied ({0}).
  139.  
  140.    MSG_ENABLING_DEBUG = Enabling Debug and BootDebug...
  141.    # {0} is a file path.
  142.    MSG_KERNEL_DEBUG_KEY_FILE = Find the kernel debugging key at: {0}
  143.  
  144.    MSG_ENABLING_EMS = Enabling EMS and BootEMS...
  145.    
  146.    MSG_ENABLING_TESTSIGNING = Enabling TestSigning...
  147.  
  148.    MSG_DISMOUNTING_IMAGE = Dismounting image...
  149.  
  150.    MSG_TARGET_IMAGE = Target image: '{0}'
  151.    
  152.    MSG_START_DEBUGGER = Before starting the debugging session, start the debugger server in remote PS session using {0}.
  153. '@
  154. }
  155.  
  156. # Import localized strings
  157. Import-LocalizedData Strings -FileName NanoServerImageGenerator.Strings.psd1 -ErrorAction SilentlyContinue
  158.  
  159. ### -----------------------------------
  160. ### Get-NanoServerPackage Cmdlet
  161. ### -----------------------------------
  162.  
  163. Function Get-NanoServerPackage
  164. {
  165.     [CmdletBinding()]
  166.     Param
  167.     (
  168.         # Location of the source media.
  169.         [String]$MediaPath,
  170.         # Where to place the copy of the source media.
  171.         [String]$BasePath
  172.     )
  173.  
  174.     Process
  175.     {
  176.         Write-LogHeader
  177.    
  178.         $Script:NewImage = $True
  179.  
  180.         # Tracking (used to handle Ctrl-C gracefully)
  181.         $HasWorkFinished = $False
  182.  
  183.         # Phase 0
  184.         try
  185.         {
  186.             if ($MediaPath -and $BasePath)
  187.             {
  188.                 Throw $Strings.ERR_ONLY_ONE_PATH_PERMITTED
  189.             }
  190.            
  191.             Initialize-PathValues `
  192.                 -BasePath $BasePath `
  193.                 -MediaPath $MediaPath
  194.             Test-Paths
  195.  
  196.             $Dirs = @()
  197.             if ($Script:HasMediaPath)
  198.             {
  199.                 $Dirs += $Script:PackagesPath
  200.                 $Dirs += $Script:LanguagePackagesPath
  201.             }
  202.             else
  203.             {
  204.                 $Dirs += $Script:BasePackagesPath
  205.                 $Dirs += $Script:BaseLanguagePackagesPath
  206.             }
  207.            
  208.             $Packages = $Dirs | ForEach-Object {
  209.                 (Get-ChildItem -Path $_ -Filter "*.cab").Name
  210.             } | % { Get-PackageName $_ } | Sort-Object | Get-Unique
  211.            
  212.             $HasWorkFinished = $True
  213.            
  214.             return $Packages
  215.         }
  216.         catch
  217.         {
  218.             Write-Log $LEVEL_WARNING ("{0}`n{1}" -f $_, $_.ScriptStackTrace) -NoOutput
  219.             Write-Log $LEVEL_WARNING ($Strings.MSG_TERMINATING_DUE_TO_ERROR -f $Script:LogFile) $True
  220.            
  221.             Throw
  222.         }
  223.     }
  224. }
  225.  
  226. ### -----------------------------------
  227. ### New-NanoServerImage Cmdlet
  228. ### -----------------------------------
  229.  
  230. Function New-NanoServerImage
  231. {
  232.     [CmdletBinding()]
  233.     Param
  234.     (
  235.         # Deployment type for the produced image.
  236.         [Parameter(Mandatory = $True, Position=0)]
  237.         [ValidateSet("Host", "Guest")]
  238.         [String]$DeploymentType,
  239.        
  240.         # Edition of the produced image.
  241.         [Parameter(Mandatory = $True, Position=1)]
  242.         [ValidateSet("Standard", "Datacenter")]
  243.         [String]$Edition,
  244.    
  245.         # Location of the source media.
  246.         [Parameter()]
  247.         [String]$MediaPath,
  248.         # Where to place the copy of the source media.
  249.         [Parameter()]
  250.         [String]$BasePath,
  251.         # Path to the produced image.
  252.         [Parameter(Mandatory = $True)]
  253.         [ValidatePattern('\.(vhdx?|wim)$')]
  254.         [String]$TargetPath,
  255.        
  256.         # Output image maximum size.
  257.         [ValidateRange(512MB, 64TB)]
  258.         [UInt64]$MaxSize = 4GB,
  259.        
  260.         # Include the Storage package.
  261.         [Switch]$Storage,
  262.         # Include the Compute (Hyper-V) package.
  263.         [Switch]$Compute,
  264.         # Include the Windows Defender package.
  265.         [Switch]$Defender,
  266.         # Include the Failover Clustering package.
  267.         [Switch]$Clustering,
  268.         # Include the OEM Drivers package.
  269.         [Switch]$OEMDrivers,
  270.         # Include the Containers package.
  271.         [Switch]$Containers,
  272.  
  273.         # Include the following packages from media.
  274.         [String[]]$Packages,
  275.         # Include the following servicing packages.
  276.         [String[]]$ServicingPackages,
  277.        
  278.         # Name to give to the target computer.
  279.         [ValidateLength(1, 15)]
  280.         [String]$ComputerName,
  281.         # Password for the administrator account of the target computer.
  282.         [Parameter(Mandatory = $True)]
  283.         [SecureString]$AdministratorPassword,
  284.         # Unattend template path
  285.         [String]$UnattendPath,
  286.  
  287.         # Name of the domain.
  288.         [ValidateNotNullOrEmpty()]
  289.         [String]$DomainName,
  290.         # Location of the domain blob.
  291.         [ValidateNotNullOrEmpty()]
  292.         [String]$DomainBlobPath,
  293.         # Force reusing a node when joining a domain.
  294.         [Switch]$ReuseDomainNode,
  295.  
  296.         # Location of additional drivers to include.
  297.         [ValidateNotNullOrEmpty()]
  298.         [String]$DriversPath,
  299.        
  300.         # Configure this interface statically.
  301.         [ValidateNotNullOrEmpty()]
  302.         [String]$InterfaceNameOrIndex,
  303.        
  304.         # Set statically IPv6 address.
  305.         [ValidateNotNullOrEmpty()]
  306.         [String]$Ipv6Address,
  307.        
  308.         # Set statically IPv6 DNS servers.
  309.         [ValidateNotNullOrEmpty()]
  310.         [String[]]$Ipv6Dns,
  311.        
  312.         # Set statically IPv4 address.
  313.         [ValidatePattern('^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$')]
  314.         [String]$Ipv4Address,
  315.        
  316.         # Set statically IPv4 subnet mask.
  317.         [ValidatePattern('^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$')]
  318.         [String]$Ipv4SubnetMask,
  319.        
  320.         # Set statically IPv4 gateway.
  321.         [ValidatePattern('^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$')]
  322.         [String]$Ipv4Gateway,
  323.        
  324.         # Set statically IPv4 DNS servers.
  325.         [ValidatePattern('^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$')]
  326.         [String[]]$Ipv4Dns,
  327.  
  328.         # Enable Debug and BootDebug in the target BCD.
  329.         [ValidateSet("Serial", "Net", "1394", "USB")]
  330.         [String]$DebugMethod,
  331.  
  332.         # Enable EMS and BootEMS in the target BCD.
  333.         [Switch]$EnableEMS,
  334.         # Port to use for EMS.
  335.         [Parameter()]
  336.         [Byte]$EMSPort = 1,
  337.         # Baud rate to use for EMS.
  338.         [Parameter()]
  339.         [UInt32]$EMSBaudRate = 115200,
  340.  
  341.         # Open port 5985 for inbound TCP connections for WinRM from any location,
  342.         # not just the domain network and the local subnet.
  343.         [Switch]$EnableRemoteManagementPort,
  344.        
  345.         # Will be copied into the root of the image.
  346.         [String[]]$CopyFiles,
  347.        
  348.         # List of commands to be executed after the setup completes.
  349.         [String[]]$SetupCompleteCommands,
  350.        
  351.         # Enable RAMDisk boot.
  352.         [Switch]$RamdiskBoot,
  353.        
  354.         # Enable for development scenarios.
  355.         [Switch]$Development
  356.     )
  357.    
  358.     DynamicParam {
  359.         $DynamicParameters = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
  360.        
  361.         if (-not (Test-Path Variable:\DebugMethod))
  362.         {
  363.             $DebugMethod = $null
  364.         }
  365.        
  366.         switch ($DebugMethod)
  367.         {
  368.             "Serial" {
  369.                 # Debug Port
  370.                 $DebugCOMPort = New-DynamicParameter "DebugCOMPort" Byte 2
  371.                 $DynamicParameters.Add($DebugCOMPort.Name, $DebugCOMPort)
  372.  
  373.                 # Debug Baud Rate
  374.                 $DebugBaudRate = New-DynamicParameter "DebugBaudRate" UInt32 115200
  375.                 $DynamicParameters.Add($DebugBaudRate.Name, $DebugBaudRate)
  376.             }
  377.  
  378.             "Net" {
  379.                 # Remote IP
  380.                 $DebugRemoteIP = New-DynamicParameter "DebugRemoteIP" String -Mandatory -NotNull
  381.                 $DynamicParameters.Add($DebugRemoteIP.Name, $DebugRemoteIP)
  382.  
  383.                 # Remote Port
  384.                 $DebugPort = New-DynamicParameter "DebugPort" UInt16 -Mandatory
  385.                 $DynamicParameters.Add($DebugPort.Name, $DebugPort)
  386.  
  387.                 # Key
  388.                 $DebugKey = New-DynamicParameter "DebugKey" String -NotNull
  389.                 $DynamicParameters.Add($DebugKey.Name, $DebugKey)
  390.             }
  391.  
  392.             "1394" {
  393.                 # Channel
  394.                 $DebugChannel = New-DynamicParameter "DebugChannel" UInt16 -Mandatory
  395.                 $DynamicParameters.Add($DebugChannel.Name, $DebugChannel)
  396.             }
  397.  
  398.             "USB" {
  399.                 # Target Name
  400.                 $DebugTargetName = New-DynamicParameter "DebugTargetName" String -Mandatory -NotNull
  401.                 $DynamicParameters.Add($DebugTargetName.Name, $DebugTargetName)
  402.             }
  403.         }
  404.  
  405.         return $DynamicParameters
  406.     }
  407.  
  408.     Process
  409.     {
  410.         Write-LogHeader
  411.    
  412.         $Script:NewImage = $True
  413.  
  414.         # Checks
  415.         Verify-UserIsAdministrator
  416.         if ($Development)
  417.         {
  418.             Verify-MSVS14U1Installed
  419.         }
  420.        
  421.         if ($DomainName -and $DomainBlobPath)
  422.         {
  423.             Throw $Strings.ERR_INCLUDE_DOMAIN_NAME_OR_DOMAIN_BLOB_PATH
  424.         }
  425.         if ($DomainBlobPath -and $ComputerName)
  426.         {
  427.             Throw $Strings.ERR_INCLUDE_COMPUTER_NAME_OR_DOMAIN_BLOB_PATH
  428.         }
  429.         if ($DomainName -and !$ComputerName)
  430.         {
  431.             Throw $Strings.ERR_DOMAIN_NAME_NO_COMPUTER_NAME
  432.         }
  433.         if (!$EnableEMS -and $PSBoundParameters.ContainsKey("EMSPort"))
  434.         {
  435.             Throw $Strings.ERR_EMS_PORT_WITH_NO_EMS
  436.         }
  437.         if ($OEMDrivers -and $DeploymentType -eq $DT_GUEST)
  438.         {
  439.             Throw ($Strings.ERR_INCLUDE_OEM_WITH_DT_GUEST -f $PACK_OEM_DRIVERS, $DT_GUEST)
  440.         }
  441.         if (($DebugMethod -eq "Serial") -and $EnableEMS -and ($DebugCOMPort.Value -eq $EMSPort))
  442.         {
  443.             Throw $Strings.ERR_DEBUG_AND_EMS_PORTS_EQUAL
  444.         }
  445.         if ($ReuseDomainNode -and (!$DomainName -and !$DomainBlobPath))
  446.         {
  447.             Throw $Strings.ERR_REUSE_NODE_WITHOUT_JOIN
  448.         }
  449.         if (($Ipv4Address -or $Ipv6Address -or $Ipv4Dns -or $Ipv6Dns) -and -not $InterfaceNameOrIndex)
  450.         {
  451.             Throw $Strings.ERR_NO_INTERFACE_SPECIFIED
  452.         }
  453.         if ($RamdiskBoot -and -not ($TargetPath -like "*.wim"))
  454.         {
  455.             Throw $Strings.ERR_RAMDISK_FOR_WIM_ONLY
  456.         }
  457.         if ($AdministratorPassword -and -not (ConvertTo-String $AdministratorPassword))
  458.         {
  459.             Throw $Strings.ERR_PASSWORD_EMPTY
  460.         }
  461.        
  462.         # Tracking (used to handle Ctrl-C gracefully)
  463.         $HasWorkFinished = $False
  464.         $HasMountedImage = $False
  465.         $HasMountedEsp = $False
  466.         $SystemPartitionGuid = $Null
  467.  
  468.         # Phase 0
  469.         $Packages = [String[]](Initialize-Packages `
  470.             -Packages $Packages `
  471.             -Storage:$Storage `
  472.             -Compute:$Compute `
  473.             -Defender:$Defender `
  474.             -Clustering:$Clustering `
  475.             -OEMDrivers:$OEMDrivers `
  476.             -GuestDrivers:($DeploymentType -eq $DT_GUEST) `
  477.             -Containers:$Containers `
  478.             -RamdiskBoot:$RamdiskBoot `
  479.             -Host:($DeploymentType -eq $DT_HOST))
  480.            
  481.         try
  482.         {
  483.             Initialize-PathValues `
  484.                 $BasePath `
  485.                 $TargetPath `
  486.                 $DriversPath `
  487.                 $DomainBlobPath `
  488.                 $Packages `
  489.                 -MediaPath $MediaPath `
  490.                 -MaxSize $MaxSize `
  491.                 -CopyFiles $CopyFiles
  492.  
  493.             Initialize-Paths
  494.             Test-Paths $Packages $ServicingPackages $UnattendPath
  495.  
  496.             # Phase 1
  497.             Copy-Files
  498.            
  499.             Convert-Image $Edition
  500.             if ($RamdiskBoot)
  501.             {
  502.                 Enable-RamdiskBoot
  503.             }
  504.            
  505.             # Phase 2
  506.             Mount-Image
  507.             $HasMountedImage = $True
  508.            
  509.             if ($Development)
  510.             {
  511.                 Add-Debugger
  512.             }
  513.            
  514.             Add-Files
  515.             Add-Packages $Packages $ServicingPackages
  516.             Add-Drivers $Development
  517.  
  518.             # Phase 3
  519.             Add-ServicingDescriptor $ComputerName $AdministratorPassword $UnattendPath
  520.             Join-Domain $ComputerName $DomainName $ReuseDomainNode
  521.  
  522.             # Phase 4
  523.             if ($EnableRemoteManagementPort -or $InterfaceNameOrIndex -or $SetupCompleteCommands -or $Development)
  524.             {
  525.                 Write-SetupComplete `
  526.                     $EnableRemoteManagementPort `
  527.                     $InterfaceNameOrIndex `
  528.                     $Ipv6Address `
  529.                     $Ipv6Dns `
  530.                     $Ipv4Address `
  531.                     $Ipv4SubnetMask `
  532.                     $Ipv4Gateway `
  533.                     $Ipv4Dns `
  534.                     $SetupCompleteCommands `
  535.                     $Development
  536.             }
  537.            
  538.             if ($DebugMethod -or $EnableEMS -or $Development)
  539.             {
  540.                 switch ($Script:ImageFormat)
  541.                 {
  542.                     "vhdx"
  543.                     {
  544.                         $SystemPartitionGuid = Mount-AsBasicData
  545.                         $HasMountedEsp = $True
  546.                         $BCDPath = "$Script:TargetMountEspPath\efi\microsoft\boot\bcd"
  547.                     }
  548.                    
  549.                     "vhd"
  550.                     {
  551.                         $BCDPath = "$Script:TargetMountPath\boot\bcd"
  552.                     }
  553.                    
  554.                     default
  555.                     {
  556.                         $BCDPath = $Null
  557.                     }
  558.                 }
  559.                
  560.                 $BCDTemplatePath = "$Script:TargetMountPath\windows\system32\config\bcd-template"
  561.                
  562.                 if ($DebugMethod)
  563.                 {
  564.                     Enable-Debug $BCDPath $BCDTemplatePath
  565.  
  566.                     switch ($DebugMethod)
  567.                     {
  568.                         "Serial" { Enable-DebugSerial $BCDPath $BCDTemplatePath $DebugCOMPort.Value $DebugBaudRate.Value }
  569.                         "Net" { Enable-DebugNet $BCDPath $BCDTemplatePath $DebugRemoteIP.Value $DebugPort.Value $DebugKey.Value }
  570.                         "1394" { Enable-DebugFirewire $BCDPath $BCDTemplatePath $DebugChannel.Value }
  571.                         "USB" { Enable-DebugUSB $BCDPath $BCDTemplatePath $DebugTargetName.Value }
  572.                     }
  573.                 }
  574.                 if ($EnableEMS)
  575.                 {
  576.                     Enable-EMS $BCDPath $BCDTemplatePath $EMSPort $EMSBaudRate
  577.                 }
  578.                 if ($Development)
  579.                 {
  580.                     Enable-TestSigning $BCDPath $BCDTemplatePath
  581.                 }
  582.             }
  583.  
  584.             if ($HasMountedEsp)
  585.             {
  586.                 Dismount-AsSystemPartition $SystemPartitionGuid
  587.                 $HasMountedEsp = $False
  588.             }
  589.        
  590.             if ($HasMountedImage)
  591.             {
  592.                 Dismount-Image
  593.                 $HasMountedImage = $False
  594.             }
  595.            
  596.             Write-Log $LEVEL_OUTPUT ($Strings.MSG_DONE -f $LogFile) $True
  597.  
  598.             $HasWorkFinished = $True
  599.         }
  600.         catch
  601.         {
  602.             Write-Log $LEVEL_WARNING ("{0}`n{1}" -f $_, $_.ScriptStackTrace) -NoOutput
  603.             Write-Log $LEVEL_WARNING ($Strings.MSG_TERMINATING_DUE_TO_ERROR -f $Script:LogFile) $True
  604.            
  605.             if ($HasMountedEsp)
  606.             {
  607.                 Dismount-AsSystemPartition $SystemPartitionGuid
  608.                 $HasMountedEsp = $False
  609.             }
  610.        
  611.             if ($HasMountedImage)
  612.             {
  613.                 Dismount-Image -Discard
  614.                 $HasMountedImage = $False
  615.             }
  616.            
  617.             Throw
  618.         }
  619.         finally
  620.         {
  621.             if ($HasWorkFinished)
  622.             {
  623.                 # All good.
  624.                 Remove-Item -Recurse -Path $Script:TargetTempPath
  625.             }
  626.         }
  627.     }
  628. }
  629.  
  630. ### -----------------------------------
  631. ### Edit-NanoServerImage Cmdlet
  632. ### -----------------------------------
  633.  
  634. Function Edit-NanoServerImage
  635. {
  636.     [CmdletBinding()]
  637.     Param
  638.     (
  639.         # Where to place the copy of the source media.
  640.         [String]$BasePath,
  641.         # Location of the image to use.
  642.         [Parameter(Mandatory = $True)]
  643.         [ValidatePattern('\.(vhdx?|wim)$')]
  644.         [String]$TargetPath,
  645.  
  646.         # Include the Storage package.
  647.         [Switch]$Storage,
  648.         # Include the Compute (Hyper-V) package.
  649.         [Switch]$Compute,
  650.         # Include the Windows Defender package.
  651.         [Switch]$Defender,
  652.         # Include the Failover Clustering package.
  653.         [Switch]$Clustering,
  654.         # Include the OEM Drivers package.
  655.         [Switch]$OEMDrivers,
  656.         # Include the Containers package.
  657.         [Switch]$Containers,
  658.  
  659.         # Include the following packages from media.
  660.         [String[]]$Packages,
  661.         # Include the following servicing packages.
  662.         [String[]]$ServicingPackages,
  663.        
  664.         # Name to give to the target computer.
  665.         [ValidateLength(1, 15)]
  666.         [String]$ComputerName,
  667.         # Password for the administrator account of the target computer.
  668.         [SecureString]$AdministratorPassword,
  669.         # Unattend template path
  670.         [String]$UnattendPath,
  671.  
  672.         # Name of the domain.
  673.         [ValidateNotNullOrEmpty()]
  674.         [String]$DomainName,
  675.         # Location of the domain blob.
  676.         [ValidateNotNullOrEmpty()]
  677.         [String]$DomainBlobPath,
  678.         # Force reusing a node when joining a domain.
  679.         [Switch]$ReuseDomainNode,
  680.  
  681.         # Location of additional drivers to include.
  682.         [ValidateNotNullOrEmpty()]
  683.         [String]$DriversPath,
  684.        
  685.         # Configure this interface statically.
  686.         [ValidateNotNullOrEmpty()]
  687.         [String]$InterfaceNameOrIndex,
  688.        
  689.         # Set statically IPv6 address.
  690.         [ValidateNotNullOrEmpty()]
  691.         [String]$Ipv6Address,
  692.        
  693.         # Set statically IPv6 DNS servers.
  694.         [ValidateNotNullOrEmpty()]
  695.         [String[]]$Ipv6Dns,
  696.        
  697.         # Set statically IPv4 address.
  698.         [ValidatePattern('^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$')]
  699.         [String]$Ipv4Address,
  700.        
  701.         # Set statically IPv4 subnet mask.
  702.         [ValidatePattern('^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$')]
  703.         [String]$Ipv4SubnetMask,
  704.        
  705.         # Set statically IPv4 gateway.
  706.         [ValidatePattern('^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$')]
  707.         [String]$Ipv4Gateway,
  708.        
  709.         # Set statically IPv4 DNS servers.
  710.         [ValidatePattern('^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$')]
  711.         [String[]]$Ipv4Dns,
  712.  
  713.         # Enable Debug and BootDebug in the target BCD.
  714.         [ValidateSet("Serial", "Net", "1394", "USB")]
  715.         [String]$DebugMethod,
  716.  
  717.         # Enable EMS and BootEMS in the target BCD.
  718.         [Switch]$EnableEMS,
  719.         # Port to use for EMS.
  720.         [Parameter()]
  721.         [Byte]$EMSPort = 1,
  722.         # Baud rate to use for EMS.
  723.         [Parameter()]
  724.         [UInt32]$EMSBaudRate = 115200,
  725.  
  726.         # Open port 5985 for inbound TCP connections for WinRM.
  727.         [Switch]$EnableRemoteManagementPort,
  728.        
  729.         # Will be copied into the root of the image.
  730.         [String[]]$CopyFiles,
  731.        
  732.         # List of commands to be executed after the setup completes.
  733.         [String[]]$SetupCompleteCommands,
  734.  
  735.         # Enable RAMDisk boot.
  736.         [Switch]$RamdiskBoot,
  737.        
  738.         # Enable for development scenarios.
  739.         [Switch]$Development,
  740.  
  741.         # Override location of language packages.
  742.         [String]$LangPackages,
  743.  
  744.         # Override location of neutral packages.
  745.         [String]$NeutralPackages
  746.     )
  747.  
  748.     DynamicParam {
  749.         $DynamicParameters = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
  750.        
  751.         if (-not (Test-Path Variable:\DebugMethod))
  752.         {
  753.             $DebugMethod = $null
  754.         }
  755.  
  756.         switch ($DebugMethod)
  757.         {
  758.             "Serial" {
  759.                 # Debug Port
  760.                 $DebugCOMPort = New-DynamicParameter "DebugCOMPort" Byte 2
  761.                 $DynamicParameters.Add($DebugCOMPort.Name, $DebugCOMPort)
  762.  
  763.                 # Debug Baud Rate
  764.                 $DebugBaudRate = New-DynamicParameter "DebugBaudRate" UInt32 115200
  765.                 $DynamicParameters.Add($DebugBaudRate.Name, $DebugBaudRate)
  766.             }
  767.  
  768.             "Net" {
  769.                 # Remote IP
  770.                 $DebugRemoteIP = New-DynamicParameter "DebugRemoteIP" String -Mandatory -NotNull
  771.                 $DynamicParameters.Add($DebugRemoteIP.Name, $DebugRemoteIP)
  772.  
  773.                 # Remote Port
  774.                 $DebugPort = New-DynamicParameter "DebugPort" UInt16 -Mandatory
  775.                 $DynamicParameters.Add($DebugPort.Name, $DebugPort)
  776.  
  777.                 # Key
  778.                 $DebugKey = New-DynamicParameter "DebugKey" String -NotNull
  779.                 $DynamicParameters.Add($DebugKey.Name, $DebugKey)
  780.             }
  781.  
  782.             "1394" {
  783.                 # Channel
  784.                 $DebugChannel = New-DynamicParameter "DebugChannel" UInt16 -Mandatory
  785.                 $DynamicParameters.Add($DebugChannel.Name, $DebugChannel)
  786.             }
  787.  
  788.             "USB" {
  789.                 # Target Name
  790.                 $DebugTargetName = New-DynamicParameter "DebugTargetName" String -Mandatory -NotNull
  791.                 $DynamicParameters.Add($DebugTargetName.Name, $DebugTargetName)
  792.             }
  793.         }
  794.  
  795.         return $DynamicParameters
  796.     }
  797.  
  798.     Process
  799.     {
  800.         Write-LogHeader
  801.    
  802.         $Script:NewImage = $False
  803.  
  804.         # Checks
  805.         Verify-UserIsAdministrator
  806.         if ($Development)
  807.         {
  808.             Verify-MSVS14U1Installed
  809.         }
  810.        
  811.         if ($DomainName -and $DomainBlobPath)
  812.         {
  813.             Throw $Strings.ERR_INCLUDE_DOMAIN_NAME_OR_DOMAIN_BLOB_PATH
  814.         }
  815.         if ($DomainBlobPath -and $ComputerName)
  816.         {
  817.             Throw $Strings.ERR_INCLUDE_COMPUTER_NAME_OR_DOMAIN_BLOB_PATH
  818.         }
  819.         if ($DomainName -and !$ComputerName)
  820.         {
  821.             Throw $Strings.ERR_DOMAIN_NAME_NO_COMPUTER_NAME
  822.         }
  823.         if (!$EnableEMS -and $PSBoundParameters.ContainsKey("EMSPort"))
  824.         {
  825.             Throw $Strings.ERR_EMS_PORT_WITH_NO_EMS
  826.         }
  827.         if (($DebugMethod -eq "Serial") -and $EnableEMS -and ($DebugCOMPort.Value -eq $EMSPort))
  828.         {
  829.             Throw $Strings.ERR_DEBUG_AND_EMS_PORTS_EQUAL
  830.         }
  831.         if ($ReuseDomainNode -and (!$DomainName -and !$DomainBlobPath))
  832.         {
  833.             Throw $Strings.ERR_REUSE_NODE_WITHOUT_JOIN
  834.         }
  835.         if (($Ipv4Address -or $Ipv6Address -or $Ipv4Dns -or $Ipv6Dns) -and -not $InterfaceNameOrIndex)
  836.         {
  837.             Throw $Strings.ERR_NO_INTERFACE_SPECIFIED
  838.         }
  839.         if ($RamdiskBoot -and -not ($TargetPath -like "*.wim"))
  840.         {
  841.             Throw $Strings.ERR_RAMDISK_FOR_WIM_ONLY
  842.         }
  843.         if ($AdministratorPassword -and -not (ConvertTo-String $AdministratorPassword))
  844.         {
  845.             Throw $Strings.ERR_PASSWORD_EMPTY
  846.         }
  847.        
  848.         # Tracking (used to handle Ctrl-C gracefully)
  849.         $HasWorkFinished = $False
  850.         $HasMountedImage = $False
  851.         $HasMountedEsp = $False
  852.         $SystemPartitionGuid = $null
  853.  
  854.         # Phase 0
  855.         $Packages = [String[]](Initialize-Packages `
  856.             -Packages $Packages `
  857.             -Storage:$Storage `
  858.             -Compute:$Compute `
  859.             -Defender:$Defender `
  860.             -Clustering:$Clustering `
  861.             -OEMDrivers:$OEMDrivers `
  862.             -Containers:$Containers `
  863.             -RamdiskBoot:$RamdiskBoot)
  864.  
  865.         try
  866.         {
  867.             Initialize-PathValues `
  868.                 $BasePath `
  869.                 $TargetPath `
  870.                 $DriversPath `
  871.                 $DomainBlobPath `
  872.                 $Packages `
  873.                 -CopyFiles $CopyFiles
  874.  
  875.             Initialize-Paths
  876.             Test-Paths $Packages $ServicingPackages $UnattendPath
  877.  
  878.             # Phase 1
  879.             if ($RamdiskBoot)
  880.             {
  881.                 Enable-RamdiskBoot
  882.             }
  883.  
  884.             # Phase 2
  885.             Mount-Image
  886.             $HasMountedImage = $True
  887.            
  888.             if ($Development)
  889.             {
  890.                 Add-Debugger
  891.             }
  892.  
  893.             Add-Files
  894.             Add-Packages $Packages $ServicingPackages
  895.             Add-Drivers $Development
  896.  
  897.             # Phase 3
  898.             Add-ServicingDescriptor $ComputerName $AdministratorPassword $UnattendPath
  899.             Join-Domain $ComputerName $DomainName $ReuseDomainNode
  900.  
  901.             # Phase 4
  902.             if ($EnableRemoteManagementPort -or $InterfaceNameOrIndex -or $SetupCompleteCommands -or $Development)
  903.             {
  904.                 Write-SetupComplete `
  905.                     $EnableRemoteManagementPort `
  906.                     $InterfaceNameOrIndex `
  907.                     $Ipv6Address `
  908.                     $Ipv6Dns `
  909.                     $Ipv4Address `
  910.                     $Ipv4SubnetMask `
  911.                     $Ipv4Gateway `
  912.                     $Ipv4Dns `
  913.                     $SetupCompleteCommands `
  914.                     $Development
  915.             }
  916.            
  917.             if ($DebugMethod -or $EnableEMS -or $Development)
  918.             {
  919.                 switch ($Script:ImageFormat)
  920.                 {
  921.                     "vhdx"
  922.                     {
  923.                         $SystemPartitionGuid = Mount-AsBasicData
  924.                         $HasMountedEsp = $True
  925.                         $BCDPath = "$Script:TargetMountEspPath\efi\microsoft\boot\bcd"
  926.                     }
  927.                    
  928.                     "vhd"
  929.                     {
  930.                         $BCDPath = "$Script:TargetMountPath\boot\bcd"
  931.                     }
  932.                    
  933.                     default
  934.                     {
  935.                         $BCDPath = $Null
  936.                     }
  937.                 }
  938.                
  939.                 $BCDTemplatePath = "$Script:TargetMountPath\windows\system32\config\bcd-template"
  940.                
  941.                 if ($DebugMethod)
  942.                 {
  943.                     Enable-Debug $BCDPath $BCDTemplatePath
  944.  
  945.                     switch ($DebugMethod)
  946.                     {
  947.                         "Serial" { Enable-DebugSerial $BCDPath $BCDTemplatePath $DebugCOMPort.Value $DebugBaudRate.Value }
  948.                         "Net" { Enable-DebugNet $BCDPath $BCDTemplatePath $DebugRemoteIP.Value $DebugPort.Value $DebugKey.Value }
  949.                         "1394" { Enable-DebugFirewire $BCDPath $BCDTemplatePath $DebugChannel.Value }
  950.                         "USB" { Enable-DebugUSB $BCDPath $BCDTemplatePath $DebugTargetName.Value }
  951.                     }
  952.                 }
  953.                 if ($EnableEMS)
  954.                 {
  955.                     Enable-EMS $BCDPath $BCDTemplatePath $EMSPort $EMSBaudRate
  956.                 }
  957.                 if ($Development)
  958.                 {
  959.                     Enable-TestSigning $BCDPath $BCDTemplatePath
  960.                 }
  961.             }
  962.            
  963.             if ($HasMountedEsp)
  964.             {
  965.                 Dismount-AsSystemPartition $SystemPartitionGuid
  966.                 $HasMountedEsp = $False
  967.             }
  968.        
  969.             if ($HasMountedImage)
  970.             {
  971.                 Dismount-Image
  972.                 $HasMountedImage = $False
  973.             }
  974.            
  975.             Write-Log $LEVEL_OUTPUT ($Strings.MSG_DONE -f $LogFile) $True
  976.  
  977.             $HasWorkFinished = $True
  978.         }
  979.         catch
  980.         {
  981.             Write-Log $LEVEL_WARNING ("{0}`n{1}" -f $_, $_.ScriptStackTrace) -NoOutput
  982.             Write-Log $LEVEL_WARNING ($Strings.MSG_TERMINATING_DUE_TO_ERROR -f $Script:LogFile) $True
  983.            
  984.             if ($HasMountedEsp)
  985.             {
  986.                 Dismount-AsSystemPartition $SystemPartitionGuid
  987.                 $HasMountedEsp = $False
  988.             }
  989.        
  990.             if ($HasMountedImage)
  991.             {
  992.                 Dismount-Image -Discard
  993.                 $HasMountedImage = $False
  994.             }
  995.  
  996.             Throw
  997.         }
  998.         finally
  999.         {
  1000.             if ($HasWorkFinished)
  1001.             {
  1002.                 # All good.
  1003.                 Remove-Item -Recurse -Path $Script:TargetTempPath
  1004.             }
  1005.         }
  1006.     }
  1007. }
  1008.  
  1009. ### -----------------------------------
  1010. ### Phase 0
  1011. ### -----------------------------------
  1012.  
  1013. Function Initialize-Packages(
  1014.     [String[]]$Packages,
  1015.     [Switch]$Storage,
  1016.     [Switch]$Compute,
  1017.     [Switch]$Defender,
  1018.     [Switch]$Clustering,
  1019.     [Switch]$OEMDrivers,
  1020.     [Switch]$GuestDrivers,
  1021.     [Switch]$Containers,
  1022.     [Switch]$RamdiskBoot,
  1023.     [Switch]$Host)
  1024. {
  1025.     $ResultPackages = @()
  1026.    
  1027.     $AdditionalPackages = @{
  1028.         $PACK_STORAGE = $Storage
  1029.         $PACK_COMPUTE = $Compute
  1030.         $PACK_DEFENDER = $Defender
  1031.         $PACK_CLUSTERING = $Clustering
  1032.         $PACK_OEM_DRIVERS = $OEMDrivers
  1033.         $PACK_GUEST_DRIVERS = $GuestDrivers
  1034.         $PACK_CONTAINERS = $Containers
  1035.         $PACK_RAMDISK = $RamdiskBoot
  1036.         $PACK_HOST = $Host
  1037.     }
  1038.    
  1039.     $AdditionalPackages.GetEnumerator() | ForEach-Object {
  1040.         if ($_.Value -and -not ($ResultPackages -Contains $_.Name))
  1041.         {
  1042.             $ResultPackages += $_.Name
  1043.         }
  1044.     }
  1045.    
  1046.     if ($Packages)
  1047.     {
  1048.         $Packages | ForEach-Object {
  1049.             if (-not ($ResultPackages -Contains $_))
  1050.             {
  1051.                 $ResultPackages += $_
  1052.             }
  1053.         }
  1054.     }
  1055.    
  1056.     return $ResultPackages
  1057. }
  1058.  
  1059. Function Get-PackageFileName(
  1060.     [String]$PackageName,
  1061.     [String]$Language)
  1062. {
  1063.     $Ext = ".cab"
  1064.  
  1065.     if ($Language -and !$Script:Internal)
  1066.     {
  1067.         $Ext = "_$Language$Ext"
  1068.     }
  1069.    
  1070.     return $PackageName + $Ext
  1071. }
  1072.  
  1073. Function Get-PackageName(
  1074.     [String]$PackageFileName)
  1075. {
  1076.     return (($PackageFileName -Split "\\")[-1] -Split "_")[0] -Replace (".cab", "")
  1077. }
  1078.  
  1079. Function Initialize-PathValues(
  1080.     [String]$BasePath,
  1081.     [String]$TargetPath,
  1082.     [String]$DriversPath,
  1083.     [String]$DomainBlobPath,
  1084.     [String[]]$Packages,
  1085.     [String]$MediaPath,
  1086.     [UInt64]$MaxSize,
  1087.     [String[]]$CopyFiles)
  1088. {
  1089.     Write-Verbose -Message $Strings.MSG_COMPUTING_PATHS
  1090.    
  1091.     # Compute the directory structure.
  1092.     # --------------------------------
  1093.  
  1094.     $Script:Internal = [Bool]$Env:NSIGTOOLS
  1095.    
  1096.     Get-PSDrive | Out-Null
  1097.  
  1098.     # Source
  1099.     $Script:HasMediaPath = [bool]$MediaPath
  1100.     if ($Script:HasMediaPath)
  1101.     {
  1102.         $Script:NanoPath = Join-Path -Path $MediaPath -ChildPath "NanoServer"
  1103.         $Script:PackagesPath = Join-Path -Path $NanoPath -ChildPath "Packages"
  1104.         $Language = (Get-ChildItem -Path $Script:PackagesPath -Directory).Name
  1105.         $Script:LanguagePackagesPath = Join-Path -Path $PackagesPath -ChildPath $Language
  1106.         $Script:SourcesPath = Join-Path -Path $MediaPath -ChildPath "sources"
  1107.     }
  1108.  
  1109.     # Base
  1110.     if (-not $BasePath)
  1111.     {
  1112.         $BasePath = Join-Path -Path $env:TEMP -ChildPath "NanoServerImageGenerator"
  1113.     }
  1114.     $Script:BasePath = $BasePath
  1115.     if (-not $Script:HasMediaPath -and -not (Test-Path $Script:BasePath) -and (-not $Script:Internal))
  1116.     {
  1117.         Throw $Strings.ERR_MEDIA_PATH_WAS_NOT_SPECIFIED
  1118.     }
  1119.  
  1120.     if ($Script:Internal)
  1121.     {
  1122.         $Script:BaseToolsPath = $Env:NSIGTOOLS
  1123.     }
  1124.     else
  1125.     {
  1126.         $Script:BaseToolsPath = Join-Path -Path $BasePath -ChildPath "Tools"
  1127.     }
  1128.    
  1129.     if (-not $Internal)
  1130.     {
  1131.         $Script:BasePackagesPath = Join-Path -Path $BasePath -ChildPath "Packages"
  1132.  
  1133.         if (-not $Script:HasMediaPath)
  1134.         {
  1135.             $Language = (Get-ChildItem -Path $Script:BasePackagesPath -Directory).Name
  1136.         }
  1137.  
  1138.         $Script:BaseLanguagePackagesPath = Join-Path -Path $Script:BasePackagesPath -ChildPath $Language
  1139.     }
  1140.  
  1141.     # Target
  1142.     if ($TargetPath)
  1143.     {
  1144.         $Script:TargetImageFilePath = $TargetPath
  1145.         $Script:TargetPath = [System.IO.Path]::GetDirectoryName($TargetPath)
  1146.         if (-not $Script:TargetPath)
  1147.         {
  1148.             $Script:TargetPath = "."
  1149.         }
  1150.         $Script:ImageFormat = [System.IO.Path]::GetExtension($TargetPath).Substring(1)
  1151.     }
  1152.  
  1153.     # Image size in bytes
  1154.     $Script:MaxSize = $MaxSize
  1155.  
  1156.     # Drivers
  1157.     $Script:DriversPath = $DriversPath
  1158.  
  1159.     # Domain
  1160.     $Script:DomainBlobPath = $DomainBlobPath
  1161.  
  1162.     # Compute the file paths.
  1163.     # -----------------------
  1164.  
  1165.     # Source
  1166.     if ($Script:Internal)
  1167.     {
  1168.         $Script:ImageConverterPath = $Null
  1169.     }
  1170.     else
  1171.     {
  1172.         $Script:ImageConverterPath = Join-Path -Path $PSScriptRoot -ChildPath $IMAGE_CONVERTER
  1173.     }
  1174.    
  1175.     if ($Script:HasMediaPath)
  1176.     {
  1177.         $Script:ImageFilePath = Join-Path -Path $Script:NanoPath -ChildPath $IMAGE_NAME;
  1178.    
  1179.         $Script:PackageFilePaths = @{}
  1180.         $Script:LanguagePackageFilePaths = @{}
  1181.  
  1182.         if ($Packages)
  1183.         {
  1184.             $Packages | ForEach-Object {
  1185.                 $Script:PackageFilePaths.Add($_, (Join-Path -Path $Script:PackagesPath -ChildPath (Get-PackageFileName $_)))
  1186.                 $Script:LanguagePackageFilePaths.Add($_, (Join-Path -Path $Script:LanguagePackagesPath -ChildPath (Get-PackageFileName $_ $Language)))
  1187.             }
  1188.         }
  1189.     }
  1190.  
  1191.     # Base
  1192.     $Script:BaseWimImageFilePath = Join-Path -Path $BasePath -ChildPath $IMAGE_NAME
  1193.     $Script:BaseDismFilePath = Join-Path -Path $Script:BaseToolsPath -ChildPath "dism.exe"
  1194.  
  1195.     $Script:BasePackageFilePaths = @{}
  1196.     $Script:BaseLanguagePackageFilePaths = @{}
  1197.  
  1198.     if ($Packages)
  1199.     {
  1200.         $Packages | ForEach-Object {
  1201.             $Script:BasePackageFilePaths.Add($_, (Join-Path -Path $Script:BasePackagesPath -ChildPath (Get-PackageFileName $_)))
  1202.             $Script:BaseLanguagePackageFilePaths.Add($_, (Join-Path -Path $Script:BaseLanguagePackagesPath -ChildPath (Get-PackageFileName $_ $Language)))
  1203.         }
  1204.     }
  1205.  
  1206.     if ($TargetPath)
  1207.     {
  1208.         $Script:BaseImageFilePath = Join-Path -Path $BasePath -ChildPath ((Split-Path -Path $BasePath -Leaf) + "." + $Script:ImageFormat)
  1209.     }
  1210.  
  1211.     # Target
  1212.     $Script:TargetTempPath = Join-Path -Path $BasePath -ChildPath "Temp"
  1213.     $Script:TargetMountPath = Join-Path -Path $Script:TargetTempPath -ChildPath ("w{0}" -f (Get-Random))
  1214.     $Script:TargetMountEspPath = Join-Path -Path $Script:TargetTempPath -ChildPath ("s{0}" -f (Get-Random))
  1215.     $Script:TargetUnattendFilePath = Join-Path -Path $Script:TargetTempPath -ChildPath "Unattend.xml"
  1216.     $Script:TargetDomainBlobPath = Join-Path -Path $Script:TargetTempPath -ChildPath "djoin.blob"
  1217.     $Script:TargetSetupCompleteFilePath = Join-Path -Path $Script:TargetTempPath -ChildPath "SetupComplete.cmd"
  1218.    
  1219.     if ($TargetPath)
  1220.     {
  1221.         $Script:TargetDebuggingKeyFilePath = Join-Path -Path $Script:TargetPath -ChildPath $KERNEL_DEBUG_KEY_FILE
  1222.     }
  1223.  
  1224.     $Script:CopyFiles = @()
  1225.     if ($CopyFiles)
  1226.     {
  1227.         $Script:CopyFiles += $CopyFiles
  1228.     }
  1229.    
  1230.     $Script:DebuggerPath = "Debugger"
  1231.     $Script:StartDebuggerScript = "StartDebugger.ps1"
  1232. }
  1233.  
  1234. Function Test-Paths([String[]]$Packages, [String[]]$ServicingPackages, [String]$UnattendPath)
  1235. {
  1236.     Write-Verbose -Message $Strings.MSG_CHECKING_PATHS
  1237.  
  1238.     if ($Script:HasMediaPath)
  1239.     {
  1240.         # Check media directory structure.
  1241.         if (!(Test-Path -Path $Script:NanoPath))
  1242.         {
  1243.             Throw ($Strings.ERR_DIRECTORY_DOES_NOT_EXIST_IN_MEDIA_DIRECTORY -f "NanoServer")
  1244.         }
  1245.         if (!(Test-Path -Path $Script:PackagesPath))
  1246.         {
  1247.             Throw ($Strings.ERR_DIRECTORY_DOES_NOT_EXIST_IN_DIRECTORY -f "Packages", "NanoServer", $Script:NanoPath)
  1248.         }
  1249.         if (!(Test-Path -Path $Script:LanguagePackagesPath))
  1250.         {
  1251.             Throw ($Strings.ERR_DIRECTORY_DOES_NOT_EXIST_IN_DIRECTORY -f $Language, "Packages", $Script:PackagesPath)
  1252.         }
  1253.  
  1254.         if (!(Test-Path -Path $Script:SourcesPath))
  1255.         {
  1256.             Throw ($Strings.ERR_DIRECTORY_DOES_NOT_EXIST_IN_MEDIA_DIRECTORY -f "Sources")
  1257.         }
  1258.        
  1259.         # Check that the Nano Server image is present in the media path.
  1260.         if (!(Test-Path -Path $Script:ImageFilePath))
  1261.         {
  1262.             Throw ($Strings.ERR_IMAGE_DOES_NOT_EXIST -f $IMAGE_NAME)
  1263.         }
  1264.     }
  1265.     else
  1266.     {
  1267.         # Check base directory structure.
  1268.         if (!(Test-Path -Path $Script:BasePath) -and !$script:Internal)
  1269.         {
  1270.             Throw $Strings.ERR_BASE_DIRECTORY_DOES_NOT_EXIST
  1271.         }
  1272.         if (!(Test-Path -Path $Script:BaseToolsPath))
  1273.         {
  1274.             Throw ($Strings.ERR_DIRECTORY_DOES_NOT_EXIST_IN_BASE_DIRECTORY -f "Tools")
  1275.         }
  1276.         if (!$Script:Internal -and !(Test-Path -Path $Script:BasePackagesPath))
  1277.         {
  1278.             Throw ($Strings.ERR_DIRECTORY_DOES_NOT_EXIST_IN_BASE_DIRECTORY -f "Packages")
  1279.         }
  1280.  
  1281.         if (!$Script:Internal -and !(Test-Path -Path $Script:BaseLanguagePackagesPath))
  1282.         {
  1283.             Write-Output -InputObject ($Strings.ERR_DIRECTORY_DOES_NOT_EXIST_IN_DIRECTORY -f $Language, "Packages", $Script:BasePackagesPath)
  1284.         }
  1285.  
  1286.         # Check that the Nano Server image is present in the base path.
  1287.         if (!(Test-Path -Path $Script:BaseWimImageFilePath) -and !$script:Internal -and $Script:NewImage)
  1288.         {
  1289.             Throw ($Strings.ERR_IMAGE_DOES_NOT_EXIST_IN_BASE_DIRECTORY -f $IMAGE_NAME)
  1290.         }
  1291.     }
  1292.  
  1293.     # Check that the existing image actually exists
  1294.     if (!$Script:HasMediaPath)
  1295.     {
  1296.         if (!$Script:NewImage -and (!$Script:TargetImageFilePath -or !(Test-Path -Path $Script:TargetImageFilePath)))
  1297.         {
  1298.             Throw $Strings.ERR_SPECIFIED_IMAGE_DOES_NOT_EXIST
  1299.         }
  1300.     }
  1301.  
  1302.     # Check that the given drivers path exists.
  1303.     if ($Script:DriversPath -and !(Test-Path -Path $Script:DriversPath))
  1304.     {
  1305.         Throw ($Strings.ERR_DRIVERS_DIRECTORY_DOES_NOT_EXIST -f $Script:DriversPath)
  1306.     }
  1307.  
  1308.     # Check that the image converter script is present.
  1309.     if ($Script:ImageConverterPath)
  1310.     {
  1311.         if (!(Test-Path -Path $Script:ImageConverterPath))
  1312.         {
  1313.             Throw ($Strings.ERR_IMAGE_CONVERTER_SCRIPT_DOES_NOT_EXIST -f $IMAGE_CONVERTER)
  1314.         }
  1315.     }
  1316.  
  1317.     if (-not $Script:Internal)
  1318.     {
  1319.         if ($Script:HasMediaPath)
  1320.         {
  1321.             # Check that the files for the requested packages are present in the media directory.
  1322.             $PackagesNotFound = $Script:PackageFilePaths.GetEnumerator() | Where-Object { (($Packages -Contains $_.Key) -and !(Test-Path -Path $_.Value)) }
  1323.             $LanguagePackagesNotFound = $Script:LanguagePackageFilePaths.GetEnumerator() | Where-Object { (($Packages -Contains $_.Key) -and !(Test-Path -Path $_.Value)) }
  1324.         }
  1325.         else
  1326.         {
  1327.             # Check that the files for the requested packages are present in the base directory.
  1328.             $PackagesNotFound = $Script:BasePackageFilePaths.GetEnumerator() | Where-Object { (($Packages -Contains $_.Key) -and !(Test-Path -Path $_.Value)) }
  1329.             $LanguagePackagesNotFound = $Script:BaseLanguagePackageFilePaths.GetEnumerator() | Where-Object { (($Packages -Contains $_.Key) -and !(Test-Path -Path $_.Value)) }
  1330.         }
  1331.     }
  1332.     else
  1333.     {
  1334.         $PackagesNotFound = $Null
  1335.         $LanguagePackagesNotFound = $Null
  1336.     }
  1337.     $ServicingPackagesNotFound = $Null
  1338.     if ($ServicingPackages)
  1339.     {
  1340.         $ServicingPackagesNotFound = $ServicingPackages | Where-Object { !(Test-Path -Path $_) }
  1341.     }
  1342.  
  1343.     if ($PackagesNotFound)
  1344.     {
  1345.         $PackagesNotFound | ForEach-Object { Write-Log $LEVEL_WARNING ($Strings.ERR_PACKAGE_DOES_NOT_EXIST -f $_.Value) }
  1346.     }
  1347.     if ($LanguagePackagesNotFound)
  1348.     {
  1349.         $LanguagePackagesNotFound | ForEach-Object { Write-Log $LEVEL_WARNING ($Strings.ERR_LANGUAGE_PACKAGE_DOES_NOT_EXIST -f $_.Value) }
  1350.     }
  1351.     if ($ServicingPackagesNotFound)
  1352.     {
  1353.         $ServicingPackagesNotFound | ForEach-Object { Write-Log $LEVEL_WARNING ($Strings.ERR_PACKAGE_DOES_NOT_EXIST -f $_) }
  1354.     }
  1355.  
  1356.     if ($PackagesNotFound -or $LanguagePackagesNotFound -or $ServicingPackagesNotFound)
  1357.     {
  1358.         Throw $Strings.ERR_ONE_OR_MORE_PACKAGES_DO_NOT_EXIST
  1359.     }
  1360.    
  1361.     # Check that the specified domain blob path exists.
  1362.     if ($Script:DomainBlobPath -and !(Test-Path -Path $Script:DomainBlobPath))
  1363.     {
  1364.         Throw $Strings.ERR_DOMAIN_BLOB_DOES_NOT_EXIST
  1365.     }
  1366.  
  1367.     if ($Script:CopyFiles -and ($Script:CopyFiles | Where-Object { !(Test-Path -Path $_) }))
  1368.     {
  1369.         Throw $Strings.ERR_COPY_FILES_DOES_NOT_EXIST
  1370.     }
  1371.    
  1372.     if ($UnattendPath -and !(Test-Path $UnattendPath))
  1373.     {
  1374.         Throw $Strings.ERR_UNATTEND_TEMPLATE_DOES_NOT_EXIST
  1375.     }
  1376. }
  1377.  
  1378. Function Initialize-Paths()
  1379. {
  1380.     Write-Verbose -Message $Strings.MSG_CREATING_PATHS
  1381.  
  1382.     New-Item -ItemType Directory -Force -Path $Script:BasePath | Write-Verbose
  1383.    
  1384.     if (!$Internal)
  1385.     {
  1386.         New-Item -ItemType Directory -Force -Path $Script:BaseToolsPath | Write-Verbose
  1387.         New-Item -ItemType Directory -Force -Path $Script:BasePackagesPath | Write-Verbose
  1388.         New-Item -ItemType Directory -Force -Path $Script:BaseLanguagePackagesPath | Write-Verbose
  1389.  
  1390.         $ResetTargetPath = $True
  1391.         if (Test-Path $Script:TargetPath)
  1392.         {
  1393.             $TP = Get-Item $Script:TargetPath
  1394.             if ($TP.Root.FullName -eq $TP.FullName)
  1395.             {
  1396.                 $ResetTargetPath = $False
  1397.             }
  1398.         }
  1399.         if ($ResetTargetPath)
  1400.         {
  1401.             New-Item -ItemType Directory -Force -Path $Script:TargetPath | Write-Verbose
  1402.         }
  1403.     }
  1404.    
  1405.     New-Item -ItemType Directory -Force -Path $Script:TargetMountPath  | Write-Verbose
  1406.     New-Item -ItemType Directory -Force -Path $Script:TargetMountEspPath  | Write-Verbose
  1407.  
  1408.     $Script:TargetMountPath = (Resolve-Path -Path $Script:TargetMountPath).ProviderPath
  1409.     $Script:TargetMountEspPath = (Resolve-Path -Path $Script:TargetMountEspPath).ProviderPath  
  1410. }
  1411.  
  1412. ### -----------------------------------
  1413. ### Phase 1
  1414. ### -----------------------------------
  1415.  
  1416. Function Copy-Files()
  1417. {
  1418.     if (!$Script:HasMediaPath)
  1419.     {
  1420.         Write-Verbose -Message $Strings.MSG_SKIPPING_FILE_COPY
  1421.  
  1422.         return
  1423.     }
  1424.  
  1425.     Write-Verbose -Message $Strings.MSG_COPYING_FILES
  1426.     Write-Progress -Activity $Strings.MSG_COPYING_FILES
  1427.  
  1428.     # Copy the tools (exclude the large, unnecessary WIM's in that folder).
  1429.     if (!$Script:Internal)
  1430.     {
  1431.         Copy-Item -Path $Script:SourcesPath\* -Destination $Script:BaseToolsPath -Exclude *.wim -Force
  1432.     }
  1433.  
  1434.     # Copy the image
  1435.     Copy-Item -Path $Script:ImageFilePath -Destination $Script:BasePath -Force
  1436.  
  1437.     # Copy the packages
  1438.     Copy-Item -Path $Script:PackagesPath\*.cab -Destination $Script:BasePackagesPath -Force
  1439.     Copy-Item -Path $Script:LanguagePackagesPath\*.cab -Destination $Script:BaseLanguagePackagesPath -Force
  1440. }
  1441.  
  1442. Function Enable-RamdiskBoot()
  1443. {
  1444.     $cmdTemplate = "& '{0}' /Export-Image /SourceImageFile:'{1}' /SourceIndex:1 /DestinationImageFile:'{1}' /Bootable /LogLevel:2 /LogPath:'{2}'"
  1445.     $cmd = ($cmdTemplate -f $Script:BaseDismFilePath, $Script:TargetImageFilePath, $Script:DismLogFile)
  1446.     Invoke-ExternalCommand $cmd
  1447.    
  1448.     $cmdTemplate = "& '{0}' /Delete-Image /ImageFile:'{1}' /Index:1 /LogLevel:2 /LogPath:'{2}'"
  1449.     $cmd = ($cmdTemplate -f $Script:BaseDismFilePath, $Script:TargetImageFilePath, $Script:DismLogFile)
  1450.     Invoke-ExternalCommand $cmd
  1451. }
  1452.  
  1453. Function Export-WimImage([String]$Edition)
  1454. {
  1455.     $cmdTemplate = "& '{0}' /Export-Image /SourceImageFile:'{1}' /SourceIndex:{2} /DestinationImageFile:'{3}' /LogLevel:2 /LogPath:'{4}'"
  1456.     $cmd = ($cmdTemplate -f $Script:BaseDismFilePath, $Script:BaseWimImageFilePath, $Script:Editions[$Edition], $Script:BaseImageFilePath, $Script:DismLogFile)
  1457.     Invoke-ExternalCommand $cmd
  1458. }
  1459.  
  1460. Function Export-VhdImage([String]$Edition)
  1461. {
  1462.     $Parameters = @{}
  1463.     $Parameters["-SourcePath"] = $Script:BaseWimImageFilePath
  1464.     $Parameters["-VHDPath"] = $Script:BaseImageFilePath
  1465.     $Parameters["-VHDFormat"] = $Script:ImageFormat
  1466.     $Parameters["-EnableDebugger"] = "None"
  1467.     $Parameters["-Edition"] = $Script:Editions[$Edition]
  1468.     if ($Script:ImageFormat -eq "vhdx")
  1469.     {
  1470.         $Parameters["-DiskLayout"] = "UEFI"
  1471.     }
  1472.     else
  1473.     {
  1474.         $Parameters["-DiskLayout"] = "BIOS"
  1475.     }
  1476.     $Parameters["-SizeBytes"] = $Script:MaxSize
  1477.  
  1478.     if (!(Get-Command Convert-WindowsImage -ErrorAction SilentlyContinue))
  1479.     {
  1480.         . $Script:ImageConverterPath
  1481.     }
  1482.  
  1483.     Convert-WindowsImage @Parameters
  1484. }
  1485.  
  1486. Function Convert-Image([String]$Edition)
  1487. {
  1488.     Write-Verbose -Message $Strings.MSG_CONVERTING_IMAGE
  1489.     Write-Progress -Activity $Strings.MSG_CONVERTING_IMAGE
  1490.  
  1491.     if ($Script:ImageFormat -eq "wim")
  1492.     {
  1493.         Export-WimImage $Edition
  1494.     }
  1495.     else
  1496.     {
  1497.         Export-VhdImage $Edition
  1498.     }  
  1499.        
  1500.     if (-not (Test-Path $Script:BaseImageFilePath))
  1501.     {
  1502.         Throw $Strings.ERR_IMAGE_WAS_NOT_PRODUCED
  1503.     }
  1504.    
  1505.     Move-Item -Path $Script:BaseImageFilePath -Destination $Script:TargetImageFilePath -Force
  1506. }
  1507.  
  1508. ### -----------------------------------
  1509. ### Phase 2
  1510. ### -----------------------------------
  1511.  
  1512. Function Add-Files()
  1513. {
  1514.     if (!$Script:CopyFiles)
  1515.     {
  1516.         Write-Verbose -Message $Strings.MSG_SKIPPING_COPYING_FILES_TO_IMAGE
  1517.    
  1518.         return
  1519.     }
  1520.    
  1521.     Write-Verbose -Message $Strings.MSG_COPYING_FILES_TO_IMAGE
  1522.     Write-Progress -Activity $Strings.MSG_COPYING_FILES_TO_IMAGE
  1523.    
  1524.     $Script:CopyFiles | ForEach-Object {
  1525.         Copy-Item -Recurse -Force -Path $_ -Destination $Script:TargetMountPath
  1526.     }
  1527. }
  1528.  
  1529. Function Add-Packages([String[]]$Packages, [String[]]$ServicingPackages)
  1530. {
  1531.     if (!$Packages -and !$ServicingPackages)
  1532.     {
  1533.         Write-Verbose -Message $Strings.MSG_SKIPPING_PACKAGE_ADDITION
  1534.        
  1535.         return
  1536.     }
  1537.  
  1538.     Write-Verbose -Message $Strings.MSG_ADDING_PACKAGES
  1539.     Write-Progress -Activity $Strings.MSG_ADDING_PACKAGES
  1540.  
  1541.     if ($Packages)
  1542.     {
  1543.         $Packages | ForEach-Object {
  1544.             Add-Package $Script:BasePackageFilePaths[$_] $False
  1545.             Add-Package $Script:BaseLanguagePackageFilePaths[$_] $True
  1546.         }
  1547.     }
  1548.    
  1549.     if ($ServicingPackages)
  1550.     {
  1551.         $ServicingPackages | ForEach-Object {
  1552.             Add-Package $_ $False
  1553.         }
  1554.     }
  1555. }
  1556.  
  1557. Function Add-Package([String]$PackageFilePath, [Bool]$IsLanguage)
  1558. {
  1559.     $PackageFilePath = (Resolve-Path "$PackageFilePath*").ProviderPath
  1560.     $PackageName = Get-PackageName $PackageFilePath
  1561.  
  1562.     if ($IsLanguage)
  1563.     {        
  1564.         $Message = $Strings.MSG_ADDING_LANGUAGE_PACKAGE -f $PackageName
  1565.     }
  1566.     else
  1567.     {
  1568.         $Message = $Strings.MSG_ADDING_PACKAGE -f $PackageName
  1569.     }
  1570.  
  1571.     Write-Progress -Id 1 -Activity $Message
  1572.  
  1573.    
  1574.     $ExitCode = Invoke-ExternalCommand "& '$Script:BaseDismFilePath' /Add-Package /PackagePath:'$PackageFilePath' /Image:'$Script:TargetMountPath' /LogLevel:2 /LogPath:'$Script:DismLogFile'" -ReturnExitCode
  1575.     if ($ExitCode)
  1576.     {
  1577.         if ($ExitCode -eq 0x800f081e)
  1578.         {
  1579.             Throw ($Strings.ERR_PACKAGE_NOT_APPLICABLE -f $PackageName)
  1580.         }
  1581.         else
  1582.         {
  1583.             Throw ($Strings.ERR_EXTERNAL_CMD -f $ExitCode)
  1584.         }
  1585.     }
  1586.  
  1587.     Write-Progress -Id 1 -Activity $Message -Completed
  1588. }
  1589.  
  1590. Function Add-Drivers([Bool]$Development)
  1591. {
  1592.     if (!$Script:DriversPath)
  1593.     {
  1594.         Write-Verbose -Message $Strings.MSG_SKIPPING_DRIVER_ADDITION
  1595.  
  1596.         return
  1597.     }
  1598.  
  1599.     Write-Verbose -Message $Strings.MSG_ADDING_DRIVERS
  1600.     Write-Progress -Activity $Strings.MSG_ADDING_DRIVERS
  1601.    
  1602.     $CmdTemplate = "& '{0}' /Add-Driver /Driver:'{1}' /Recurse /Image:'{2}' /LogLevel:2 /LogPath:'{3}' /ForceUnsigned"
  1603.     $Cmd = $CmdTemplate -f $Script:BaseDismFilePath, $Script:DriversPath, $Script:TargetMountPath, $Script:DismLogFile
  1604.    
  1605.     if ($Development)
  1606.     {
  1607.         $Cmd += " /ForceUnsigned"
  1608.     }
  1609.    
  1610.     Invoke-ExternalCommand $Cmd
  1611. }
  1612.  
  1613. Function Add-Debugger()
  1614. {
  1615.     Write-Verbose -Message $Strings.MSG_ADDING_DEBUGGER
  1616.     Write-Progress -Activity $Strings.MSG_ADDING_DEBUGGER
  1617.    
  1618.     Copy-Item -Path $VSDebuggerBinariesPath -Destination (Join-Path -Path $Script:TargetMountPath -ChildPath $Script:DebuggerPath) -Recurse
  1619.    
  1620.     $System32Path = Join-Path -Path $Script:TargetMountPath -ChildPath "Windows\System32"
  1621.    
  1622.     Copy-Item -Path (Join-Path -Path $Script:VSCRTReleaseBinariesPath -ChildPath "vcruntime140.dll") -Destination $System32Path
  1623.     Copy-Item -Path (Join-Path -Path $Script:VSCRTReleaseBinariesPath -ChildPath "msvcp140.dll") -Destination $System32Path
  1624.     Copy-Item -Path (Join-Path -Path $Script:VSCRTDebugBinariesPath -ChildPath "vcruntime140d.dll") -Destination $System32Path
  1625.     Copy-Item -Path (Join-Path -Path $Script:VSCRTDebugBinariesPath -ChildPath "msvcp140d.dll") -Destination $System32Path
  1626.     Copy-Item -Path (Join-Path -Path $Script:VSUCRTBinariesPath -ChildPath "ucrtbased.dll") -Destination $System32Path
  1627.    
  1628.     $StartDebuggerPath = Join-Path -Path $Script:TargetMountPath -ChildPath $Script:StartDebuggerScript
  1629.     Write-Output ("Start `$Env:SystemDrive\{0}\msvsmon.exe -ArgumentList /nowowwarn,/noauth,/anyuser,/nosecuritywarn,/timeout:36000" -f $Script:DebuggerPath) | Out-File -FilePath $StartDebuggerPath
  1630.    
  1631.     Write-Log $LEVEL_OUTPUT ($Strings.MSG_START_DEBUGGER -f (Join-Path -Path "C:\" -ChildPath $Script:StartDebuggerScript))
  1632.    
  1633.     Mount-RegistryHive "SOFTWARE"
  1634.     Try
  1635.     {
  1636.         Add-Registry "Policies\Microsoft\Windows\Appx" "/v AllowDevelopmentWithoutDevLicense /t REG_DWORD /d 1 /f"
  1637.         Add-Registry "Microsoft\Windows\CurrentVersion\AppModelUnlock" "/v AllowAllTrustedApps /t REG_DWORD /d 1 /f"
  1638.         Add-Registry "Microsoft\Windows\CurrentVersion\AppModelUnlock" "/v AllowDevelopmentWithoutDevLicense /t REG_DWORD /d 1 /f"
  1639.     }
  1640.     Finally
  1641.     {
  1642.         Unmount-RegistryHive
  1643.     }
  1644. }
  1645.  
  1646. ### -----------------------------------
  1647. ### Phase 3
  1648. ### -----------------------------------
  1649.  
  1650. Function ConvertTo-String([SecureString]$SecureString)
  1651. {
  1652.     if (-not $SecureString)
  1653.     {
  1654.         return $null
  1655.     }
  1656.  
  1657.     $PointerToPasswordString = $Null
  1658.     try
  1659.     {
  1660.         $PointerToPasswordString = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($SecureString)
  1661.         $ManagedPasswordString = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($PointerToPasswordString)
  1662.     }
  1663.     finally
  1664.     {
  1665.         [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($PointerToPasswordString)
  1666.     }
  1667.    
  1668.     return $ManagedPasswordString
  1669. }
  1670.  
  1671. Function Add-ServicingDescriptor([String]$ComputerName, [SecureString]$AdministratorPassword, [String]$UnattendPath)
  1672. {
  1673.     if (-not $ComputerName -and $Script:NewImage)
  1674.     {
  1675.         $ComputerName = "*"
  1676.     }
  1677.  
  1678.     if (-not $ComputerName -and -not $AdministratorPassword -and -not $UnattendPath)
  1679.     {
  1680.         return
  1681.     }
  1682.    
  1683.     Write-Verbose -Message $Strings.MSG_ADDING_UNATTEND
  1684.     Write-Progress -Activity $Strings.MSG_ADDING_UNATTEND
  1685.    
  1686.     if (-not $Script:NewImage -and $UnattendPath)
  1687.     {
  1688.         Write-Warning ($Strings.MSG_SETUPCOMPLETE_CHANGES_FOR_BOOTED_MEDIA -f "-UnattendPath")
  1689.     }
  1690.    
  1691.     # Write out the unattend.
  1692.     New-Unattend $ComputerName $AdministratorPassword
  1693.    
  1694.     # Embed the descriptor into the image.
  1695.     Invoke-ExternalCommand "& '$Script:BaseDismFilePath' /Image:'$Script:TargetMountPath' /Apply-Unattend:'$Script:TargetUnattendFilePath' /LogLevel:2 /LogPath:'$Script:DismLogFile'"
  1696.    
  1697.     # Copy the unattend over.
  1698.     if ($UnattendPath)
  1699.     {
  1700.         Invoke-ExternalCommand "& '$Script:BaseDismFilePath' /Image:'$Script:TargetMountPath' /Apply-Unattend:'$UnattendPath' /LogLevel:2 /LogPath:'$Script:DismLogFile'"
  1701.         New-Item -ItemType Directory -Force -Path $Script:TargetMountPath\Windows\Panther | Out-Null
  1702.         Copy-Item -Path $UnattendPath -Destination $Script:TargetMountPath\Windows\Panther\Unattend.xml -Force
  1703.     }
  1704. }
  1705.  
  1706. Function New-Unattend([String]$ComputerName, [SecureString]$AdministratorPassword)
  1707. {
  1708.     $Xml = New-Object -TypeName Xml
  1709.     $XmlNs = "urn:schemas-microsoft-com:unattend"
  1710.    
  1711.     $XmlDecl = $Xml.CreateXmlDeclaration("1.0", "utf-8", $Null)
  1712.     $XmlRoot = $Xml.DocumentElement
  1713.     $Xml.InsertBefore($XmlDecl, $XmlRoot) | Out-Null
  1714.  
  1715.     $XmlUnattended = $Xml.CreateElement("unattend", $XmlNs)
  1716.     $XmlUnattended.SetAttribute("xmlns:wcm", "http://schemas.microsoft.com/WMIConfig/2002/State")
  1717.     $XmlUnattended.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
  1718.     $Xml.AppendChild($XmlUnattended) | Out-Null
  1719.  
  1720.     if ($AdministratorPassword)
  1721.     {
  1722.         Write-AdministratorPasswordXml $Xml $XmlNs $XmlUnattended $AdministratorPassword
  1723.     }
  1724.  
  1725.     if ($ComputerName)
  1726.     {
  1727.         Write-ComputerNameXml $Xml $XmlNs $XmlUnattended $ComputerName
  1728.     }
  1729.    
  1730.     # Normal .NET methods are unaware of the PowerShell context. In this case,
  1731.     # the path to save the XML to is relative. While PowerShell would resolve
  1732.     # it as we would expect, the .NET method will do so relative to its working
  1733.     # directory, which is not necessarily the same as PowerShell's. So, we need
  1734.     # to expand the relative path manually.
  1735.     $TargetPath = $Script:TargetUnattendFilePath
  1736.     if (-not [System.IO.Path]::IsPathRooted($TargetPath))
  1737.     {
  1738.         $TargetPath = Join-Path -Path (Get-Location) -ChildPath $Script:TargetUnattendFilePath
  1739.     }
  1740.    
  1741.     $Xml.Save([System.IO.Path]::GetFullPath($TargetPath))
  1742. }
  1743.  
  1744. Function Write-ComputerNameXml([Xml]$Xml, [String]$XmlNs, [System.Xml.XmlElement]$XmlUnattended, [String]$ComputerName)
  1745. {
  1746.     if (-not (Get-Member -InputObject $XmlUnattended -Name "settings"))
  1747.     {
  1748.         $XmlSettings = $Xml.CreateElement("settings", $XmlNs)
  1749.         $XmlSettings.SetAttribute("pass", "offlineServicing")
  1750.         $XmlUnattended.AppendChild($XmlSettings) | Out-Null
  1751.     }
  1752.     $XmlSettings = $XmlUnattended.settings
  1753.    
  1754.     if (-not (Get-Member -InputObject $XmlSettings -Name "component"))
  1755.     {
  1756.         $XmlComponent = $Xml.CreateElement("component", $XmlNs)
  1757.         $XmlComponent.SetAttribute("name", "Microsoft-Windows-Shell-Setup")
  1758.         $XmlComponent.SetAttribute("processorArchitecture", "amd64")
  1759.         $XmlComponent.SetAttribute("publicKeyToken", "31bf3856ad364e35")
  1760.         $XmlComponent.SetAttribute("language", "neutral")
  1761.         $XmlComponent.SetAttribute("versionScope", "nonSxS")
  1762.         $XmlSettings.AppendChild($XmlComponent) | Out-Null
  1763.     }
  1764.     $XmlComponent = $XmlSettings.component
  1765.  
  1766.     $XmlComputerName = $Xml.CreateElement("ComputerName", $XmlNs)
  1767.     $XmlName = $Xml.CreateTextNode($ComputerName)
  1768.     $XmlComputerName.AppendChild($XmlName) | Out-Null
  1769.     $XmlComponent.AppendChild($XmlComputerName) | Out-Null
  1770. }
  1771.  
  1772. Function Write-AdministratorPasswordXml([Xml]$Xml, [String]$XmlNs, [System.Xml.XmlElement]$XmlUnattended, [SecureString]$AdministratorPassword)
  1773. {
  1774.     $XmlSettings = $Xml.CreateElement("settings", $XmlNs)
  1775.     $XmlSettings.SetAttribute("pass", "offlineServicing")
  1776.     $XmlUnattended.AppendChild($XmlSettings) | Out-Null
  1777.     $XmlComponent = $Xml.CreateElement("component", $XmlNs)
  1778.     $XmlComponent.SetAttribute("name", "Microsoft-Windows-Shell-Setup")
  1779.     $XmlComponent.SetAttribute("processorArchitecture", "amd64")
  1780.     $XmlComponent.SetAttribute("publicKeyToken", "31bf3856ad364e35")
  1781.     $XmlComponent.SetAttribute("language", "neutral")
  1782.     $XmlComponent.SetAttribute("versionScope", "nonSxS")
  1783.     $XmlSettings.AppendChild($XmlComponent) | Out-Null
  1784.     $XmlUserAccounts = $Xml.CreateElement("OfflineUserAccounts", $XmlNs)
  1785.     $XmlComponent.AppendChild($XmlUserAccounts) | Out-Null
  1786.  
  1787.     $XmlAdministratorPassword = $Xml.CreateElement("OfflineAdministratorPassword", $XmlNs)
  1788.     $XmlUserAccounts.AppendChild($XmlAdministratorPassword) | Out-Null
  1789.    
  1790.     $XmlValue = $Xml.CreateElement("Value", $XmlNs)
  1791.     $XmlText = $Xml.CreateTextNode([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes((ConvertTo-String $AdministratorPassword) + "OfflineAdministratorPassword")))
  1792.     $XmlValue.AppendChild($XmlText) | Out-Null
  1793.     $XmlAdministratorPassword.AppendChild($XmlValue) | Out-Null
  1794.  
  1795.     $XmlPlainText = $Xml.CreateElement("PlainText", $XmlNs)
  1796.     $XmlPassword = $Xml.CreateTextNode("false")
  1797.     $XmlPlainText.AppendChild($XmlPassword) | Out-Null
  1798.     $XmlAdministratorPassword.AppendChild($XmlPlainText) | Out-Null
  1799. }
  1800.  
  1801. Function Join-Domain([String]$ComputerName, [String]$DomainName, [Bool]$ReuseDomainNode)
  1802. {
  1803.     # If the target image must join a domain, but a blob was not provided, one
  1804.     # must be harvested from the local machine.
  1805.     if ($DomainName -and !$Script:DomainBlobPath)
  1806.     {
  1807.         Write-Verbose -Message $Strings.MSG_COLLECTING_DOMAIN_BLOB
  1808.  
  1809.         $Command = "djoin /Provision /Domain $DomainName /Machine $ComputerName /SaveFile '$Script:TargetDomainBlobPath'"
  1810.         if ($ReuseDomainNode)
  1811.         {
  1812.             $Command += " /Reuse"
  1813.         }
  1814.  
  1815.         try
  1816.         {
  1817.             Invoke-ExternalCommand $Command
  1818.         }
  1819.         catch
  1820.         {
  1821.             if ($LastExitCode -eq 2224)
  1822.             {
  1823.                 Throw $Strings.ERR_REUSE_DOMAIN_NAME
  1824.             }
  1825.             else
  1826.             {
  1827.                 Throw
  1828.             }
  1829.         }
  1830.  
  1831.         $Script:DomainBlobPath = $Script:TargetDomainBlobPath
  1832.     }
  1833.  
  1834.     if ($Script:DomainBlobPath)
  1835.     {
  1836.         Write-Verbose -Message $Strings.MSG_JOINING_DOMAIN
  1837.  
  1838.         Invoke-ExternalCommand "djoin /RequestODJ /LoadFile '$Script:DomainBlobPath' /WindowsPath '$Script:TargetMountPath\Windows'"
  1839.     }
  1840. }
  1841.  
  1842. ### -----------------------------------
  1843. ### Phase 4
  1844. ### -----------------------------------
  1845.  
  1846. Function Enable-TestSigning([String]$BCDPath, [String]$BCDTemplatePath)
  1847. {
  1848.     Write-Verbose -Message $Strings.MSG_ENABLING_TESTSIGNING
  1849.     Write-Progress -Activity $Strings.MSG_ENABLING_TESTSIGNING
  1850.  
  1851.     if ($BCDPath)
  1852.     {
  1853.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /set ``{default``} TestSigning ON")
  1854.     }
  1855.    
  1856.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /set $BCD_TEMPLATE_PCAT_ENTRY TestSigning ON")
  1857.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /set $BCD_TEMPLATE_EFI_ENTRY TestSigning ON")
  1858. }
  1859.  
  1860.  
  1861. Function Enable-Debug([String]$BCDPath, [String]$BCDTemplatePath)
  1862. {
  1863.     Write-Verbose -Message $Strings.MSG_ENABLING_DEBUG
  1864.     Write-Progress -Activity $Strings.MSG_ENABLING_DEBUG
  1865.  
  1866.     if ($BCDPath)
  1867.     {
  1868.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /BootDebug ``{bootmgr``} ON")
  1869.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /BootDebug ``{default``} ON")
  1870.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /Debug ``{default``} ON")
  1871.     }
  1872.    
  1873.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /BootDebug ``{bootmgr``} ON")
  1874.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /BootDebug $BCD_TEMPLATE_PCAT_ENTRY ON")
  1875.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /Debug $BCD_TEMPLATE_PCAT_ENTRY ON")
  1876.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /BootDebug $BCD_TEMPLATE_EFI_ENTRY ON")
  1877.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /Debug $BCD_TEMPLATE_EFI_ENTRY ON")
  1878. }
  1879.  
  1880. Function Enable-DebugSerial([String]$BCDPath, [String]$BCDTemplatePath, [Int]$Port, [Int]$BaudRate)
  1881. {
  1882.     if ($BCDPath)
  1883.     {
  1884.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /DBGSettings SERIAL DEBUGPORT:$Port BAUDRATE:$BaudRate")
  1885.     }
  1886.    
  1887.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /DBGSettings SERIAL DEBUGPORT:$Port BAUDRATE:$BaudRate")
  1888. }
  1889.  
  1890. Function Extract-KdnetKey([String]$BcdEditOutput)
  1891. {
  1892.     if ($BcdEditOutput -Match "^Key=(.+)$")
  1893.     {
  1894.         return $Matches[1]
  1895.     }
  1896.     else
  1897.     {
  1898.         Throw $Strings.ERR_KDNET_KEY_NOT_PRODUCED
  1899.     }
  1900. }
  1901.  
  1902. Function Enable-DebugNet([String]$BCDPath, [String]$BCDTemplatePath, [String]$RemoteIP, [String]$RemotePort, [String]$Key)
  1903. {
  1904.     if ($BCDPath)
  1905.     {
  1906.         $Command = "bcdedit /Store '$BCDPath' /DBGSettings NET HOSTIP:$RemoteIP PORT:$RemotePort"
  1907.         if ($Key)
  1908.         {
  1909.             $Command += " KEY:$Key"
  1910.         }
  1911.  
  1912.         $Key = Extract-KdnetKey (Invoke-ExternalCommand $Command -ReturnOutput -DisableRedirect)
  1913.     }
  1914.    
  1915.     $Command = "bcdedit /Store '$BCDTemplatePath' /DBGSettings NET HOSTIP:$RemoteIP PORT:$RemotePort"
  1916.     if ($Key)
  1917.     {
  1918.         $Command += " KEY:$Key"
  1919.     }
  1920.     Extract-KdnetKey (Invoke-ExternalCommand $Command -ReturnOutput -DisableRedirect) | Out-File -FilePath $Script:TargetDebuggingKeyFilePath
  1921.    
  1922.     Write-Log $LEVEL_OUTPUT ($Strings.MSG_KERNEL_DEBUG_KEY_FILE -f $Script:TargetDebuggingKeyFilePath)
  1923. }
  1924.  
  1925. Function Enable-DebugFirewire([String]$BCDPath, [String]$BCDTemplatePath, [Int]$Channel)
  1926. {
  1927.     if ($BCDPath)
  1928.     {
  1929.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /DBGSettings 1394 Channel:$Channel")
  1930.     }
  1931.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /DBGSettings 1394 Channel:$Channel")
  1932. }
  1933.  
  1934. Function Enable-DebugUSB([String]$BCDPath, [String]$BCDTemplatePath, [String]$TargetName)
  1935. {
  1936.     if ($BCDPath)
  1937.     {
  1938.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /DBGSettings USB TargetName:$TargetName")
  1939.     }
  1940.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /DBGSettings USB TargetName:$TargetName")
  1941. }
  1942.  
  1943. Function Enable-EMS([String]$BCDPath, [String]$BCDTemplatePath, [Int]$Port, [Int]$BaudRate)
  1944. {
  1945.     Write-Verbose -Message $Strings.MSG_ENABLING_EMS
  1946.     Write-Progress -Activity $Strings.MSG_ENABLING_EMS
  1947.  
  1948.     if ($BCDPath)
  1949.     {
  1950.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /EMSSettings EMSPORT:$Port EMSBAUDRATE:$BaudRate")
  1951.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /EMS ``{default``} ON")
  1952.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /BootEMS ``{default``} ON")
  1953.         Invoke-ExternalCommand("bcdedit /Store '$BCDPath' /BootEMS ``{bootmgr``} ON")
  1954.     }
  1955.    
  1956.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /EMSSettings EMSPORT:$Port EMSBAUDRATE:$BaudRate")
  1957.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /EMS $BCD_TEMPLATE_PCAT_ENTRY ON")
  1958.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /BootEMS $BCD_TEMPLATE_PCAT_ENTRY ON")
  1959.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /EMS $BCD_TEMPLATE_EFI_ENTRY ON")
  1960.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /BootEMS $BCD_TEMPLATE_EFI_ENTRY ON")
  1961.     Invoke-ExternalCommand("bcdedit /Store '$BCDTemplatePath' /BootEMS ``{bootmgr``} ON")
  1962. }
  1963.  
  1964. Function Write-SetupComplete(
  1965.     [Bool]$OpenPort,
  1966.     [String]$InterfaceNameOrIndex,
  1967.     [String]$Ipv6Address,
  1968.     [String[]]$Ipv6Dns,
  1969.     [String]$Ipv4Address,
  1970.     [String]$Ipv4SubnetMask,
  1971.     [String]$Ipv4Gateway,
  1972.     [String[]]$Ipv4Dns,
  1973.     [String[]]$SetupCompleteCommands,
  1974.     [Bool]$Development)
  1975. {
  1976.     if (-not $Script:NewImage)
  1977.     {
  1978.         Write-Warning ($Strings.MSG_SETUPCOMPLETE_CHANGES_FOR_BOOTED_MEDIA -f "-InterfaceNameOrIndex, -Ipv6Address, -Ipv6Dns, -Ipv4Address, -Ipv4SubnetMask, -Ipv4Gateway, -Ipv4Dns, -SetupCompleteCommands, -Development")
  1979.     }
  1980.  
  1981.     $Scripts = "$Script:TargetMountPath\Windows\Setup\Scripts"
  1982.     $SetupComplete = Join-Path $Scripts "setupcomplete.cmd"
  1983.     if (Test-Path ($SetupComplete))
  1984.     {
  1985.         $SetupCompleteCommandsAll = Get-Content $SetupComplete
  1986.     }
  1987.     else
  1988.     {
  1989.         New-Item -ItemType Directory -Force -Path $Scripts | Out-Null
  1990.         $SetupCompleteCommandsAll = @("@ECHO OFF")
  1991.     }
  1992.    
  1993.     if ($SetupCompleteCommands)
  1994.     {
  1995.         $SetupCompleteCommandsAll += $SetupCompleteCommands
  1996.     }
  1997.    
  1998.     if ($Ipv6Address)
  1999.     {
  2000.         $SetupCompleteCommandsAll += "netsh interface ipv6 set address `"$InterfaceNameOrIndex`" $Ipv6Address"
  2001.     }
  2002.     if ($Ipv6Dns)
  2003.     {
  2004.         $first = $Ipv6Dns[0]
  2005.         $SetupCompleteCommandsAll += "netsh interface ipv6 set dnsservers `"$InterfaceNameOrIndex`" static $first"
  2006.         for ($i = 2; $i -le $Ipv6Dns.Count; $i++)
  2007.         {
  2008.             $next = $Ipv6Dns[$i - 1]
  2009.             $SetupCompleteCommandsAll += "netsh interface ipv6 add dnsservers `"$InterfaceNameOrIndex`" $next index=$i"
  2010.         }
  2011.     }
  2012.     if ($Ipv4Address)
  2013.     {
  2014.         $SetupCompleteCommandsAll += "netsh interface ipv4 set address `"$InterfaceNameOrIndex`" static $Ipv4Address $Ipv4SubnetMask $Ipv4Gateway"
  2015.     }
  2016.     if ($Ipv4Dns)
  2017.     {
  2018.         $first = $Ipv4Dns[0]
  2019.         $SetupCompleteCommandsAll += "netsh interface ipv4 set dnsservers `"$InterfaceNameOrIndex`" static $first"
  2020.         for ($i = 2; $i -le $Ipv4Dns.Count; $i++)
  2021.         {
  2022.             $next = $Ipv4Dns[$i - 1]
  2023.             $SetupCompleteCommandsAll += "netsh interface ipv4 add dnsservers `"$InterfaceNameOrIndex`" $next index=$i"
  2024.         }
  2025.     }
  2026.     if ($OpenPort)
  2027.     {
  2028.         $SetupCompleteCommandsAll += "netsh advfirewall firewall add rule name=`"WinRM 5985`" protocol=TCP dir=in localport=5985 profile=any action=allow"
  2029.     }
  2030.    
  2031.     if ($Development)
  2032.     {
  2033.         $cmd = "netsh advfirewall firewall add rule name=`"Remote Debugger`" dir=in action=allow program=`"%SystemDrive%\{0}`" enable=yes"
  2034.         $SetupCompleteCommandsAll += ($cmd -f (Join-Path -Path $Script:DebuggerPath -ChildPath "msvsmon.exe"))
  2035.     }
  2036.    
  2037.     $SetupCompleteCommand = ($SetupCompleteCommandsAll -Join "`r`n")
  2038.    
  2039.     # To populate the batch file, the > and >> operators cannot be used. The
  2040.     # resulting file must be encoded in ASCII.
  2041.     Set-Content -Value $SetupCompleteCommand -Path "$Script:TargetSetupCompleteFilePath" -Encoding ASCII
  2042.    
  2043.     Copy-Item -Path $Script:TargetSetupCompleteFilePath -Destination "$Script:TargetMountPath\Windows\Setup\Scripts" -Force
  2044. }
  2045.  
  2046. ### -----------------------------------
  2047. ### Helper Methods
  2048. ### -----------------------------------
  2049.  
  2050. Function Mount-RegistryHive([String]$Hive)
  2051. {
  2052.     $Script:RegistryPrefix = "NSIG"
  2053.  
  2054.     $Script:RegMountPoint = "HKLM\{0}" -f $script:RegistryPrefix
  2055.     Invoke-ExternalCommand ("reg load {0} '{1}\Windows\System32\Config\{2}'" -f $script:RegMountPoint, $Script:TargetMountPath, $Hive)
  2056. }
  2057.  
  2058. Function Unmount-RegistryHive()
  2059. {
  2060.     Invoke-ExternalCommand ("reg unload {0}" -f $script:RegMountPoint)
  2061. }
  2062.  
  2063. Function Add-Registry([String]$Where, [String]$What)
  2064. {
  2065.     Invoke-ExternalCommand ("reg add '{0}\{1}' {2}" -f $Script:RegMountPoint, $Where, $What)
  2066. }
  2067.  
  2068. Function Mount-Image()
  2069. {
  2070.     Write-Verbose -Message $Strings.MSG_MOUNTING_IMAGE
  2071.     Write-Progress -Activity $Strings.MSG_MOUNTING_IMAGE
  2072.  
  2073.     $Script:TargetImageFilePath = (Resolve-Path -Path $Script:TargetImageFilePath).ProviderPath
  2074.     if ($Script:ImageFormat -eq "vhdx")
  2075.     {
  2076.         Mount-ImageInternal
  2077.     }
  2078.     else
  2079.     {
  2080.         Invoke-ExternalCommand "& '$Script:BaseDismFilePath' /Mount-Image /ImageFile:'$Script:TargetImageFilePath' /MountDir:'$Script:TargetMountPath' -Index:1 /LogLevel:2 /LogPath:'$Script:DismLogFile'"
  2081.     }
  2082. }
  2083.  
  2084. Function Dismount-Image([Switch]$Discard)
  2085. {
  2086.     Write-Verbose -Message $Strings.MSG_DISMOUNTING_IMAGE
  2087.     Write-Progress -Activity $Strings.MSG_DISMOUNTING_IMAGE
  2088.  
  2089.     if ($Script:ImageFormat -eq "vhdx")
  2090.     {
  2091.         Dismount-ImageInternal
  2092.     }
  2093.     else
  2094.     {
  2095.         if ($Discard)
  2096.         {
  2097.             Invoke-ExternalCommand "& '$Script:BaseDismFilePath' /Unmount-Image /MountDir:'$Script:TargetMountPath' /Discard /LogLevel:2 /LogPath:'$Script:DismLogFile'"
  2098.         }
  2099.         else
  2100.         {
  2101.             Invoke-ExternalCommand "& '$Script:BaseDismFilePath' /Unmount-Image /MountDir:'$Script:TargetMountPath' /Commit /LogLevel:2 /LogPath:'$Script:DismLogFile'"
  2102.         }
  2103.     }
  2104. }
  2105.  
  2106. Function Mount-ImageInternal()
  2107. {
  2108.     Mount-DiskImage -ImagePath $Script:TargetImageFilePath -NoDriveLetter
  2109.     try
  2110.     {
  2111.         $disk = Get-DiskImage -ImagePath $Script:TargetImageFilePath | Get-Disk
  2112.         $partition = $disk | Get-Partition | Where-Object { $_.Type -eq "IFS" -or $_.Type -eq "Basic" }
  2113.         $partition | Add-PartitionAccessPath -AccessPath $Script:TargetMountPath -ErrorAction Stop
  2114.     }
  2115.     catch
  2116.     {
  2117.         Dismount-DiskImage -ImagePath $Script:TargetImageFilePath
  2118.     }
  2119. }
  2120.  
  2121. Function Dismount-ImageInternal()
  2122. {
  2123.     try
  2124.     {
  2125.         $disk = Get-DiskImage -ImagePath $Script:TargetImageFilePath | Get-Disk
  2126.         $partition = $disk | Get-Partition | Where-Object { $_.Type -eq "IFS" -or $_.Type -eq "Basic" }
  2127.         $partition | Remove-PartitionAccessPath -AccessPath $Script:TargetMountPath -ErrorAction Stop
  2128.     }
  2129.     finally
  2130.     {
  2131.         Dismount-DiskImage -ImagePath $Script:TargetImageFilePath
  2132.     }
  2133. }
  2134.  
  2135. Function Mount-AsBasicData()
  2136. {
  2137.     $disk = Get-DiskImage -ImagePath $Script:TargetImageFilePath | Get-Disk
  2138.     $diskId = $disk.Number
  2139.     $partition = $disk | Get-Partition | Where-Object { $_.Type -eq "System" }
  2140.     $partId = $partition.PartitionNumber
  2141.     $partGuid = $partition.Guid
  2142.    
  2143.     $DiskpartScript = Join-Path -Path $Script:TargetTempPath -ChildPath "diskpart.txt"
  2144.    
  2145.     $DiskpartCommands = "select disk $diskId`nselect partition $partId`nset id=ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"
  2146.     $DiskpartCommands | Out-File -Encoding ASCII -FilePath $DiskpartScript
  2147.  
  2148.     Invoke-ExternalCommand "diskpart.exe /s $DiskpartScript"
  2149.    
  2150.     $partition | Add-PartitionAccessPath -AccessPath $Script:TargetMountEspPath -ErrorAction Stop
  2151.     Remove-Item $DiskpartScript
  2152.    
  2153.     return $partGuid
  2154. }
  2155.  
  2156. Function Dismount-AsSystemPartition([String]$guid)
  2157. {
  2158.     $partition = Get-Partition | Where-Object { $_.Guid -eq $guid }
  2159.     $diskId = $partition.DiskNumber
  2160.     $partId = $partition.PartitionNumber
  2161.    
  2162.     $partition | Remove-PartitionAccessPath -AccessPath $Script:TargetMountEspPath -ErrorAction Stop
  2163.    
  2164.     $DiskpartScript = Join-Path -Path $Script:TargetTempPath -ChildPath "diskpart.txt"
  2165.  
  2166.     $DiskpartCommands = "select disk $diskId`nselect partition $partId`nset id=c12a7328-f81f-11d2-ba4b-00a0c93ec93b"
  2167.     $DiskpartCommands | Out-File -Encoding ASCII -FilePath $DiskpartScript
  2168.     Invoke-ExternalCommand "diskpart.exe /s $DiskpartScript"
  2169.    
  2170.     Remove-Item $DiskpartScript
  2171. }
  2172.  
  2173. Function Invoke-ExternalCommand([String]$Command, [Switch]$ReturnOutput, [Switch]$DisableRedirect, [Switch]$ReturnExitCode)
  2174. {
  2175.     Write-Log $LEVEL_VERBOSE $Command
  2176.     if (-not $DisableRedirect)
  2177.     {
  2178.         $Command += " 2>&1"
  2179.     }
  2180.     $Output = (Invoke-Expression -Command $Command | Out-String)
  2181.     if ($LastExitCode)
  2182.     {
  2183.         Write-Log $LEVEL_NONE $Output -Verbose
  2184.  
  2185.         if (-not $ReturnExitCode)
  2186.         {
  2187.             Throw ($Strings.ERR_EXTERNAL_CMD -f $LastExitCode)
  2188.         }
  2189.     }
  2190.    
  2191.     if ($ReturnOutput)
  2192.     {
  2193.         return $Output
  2194.     }
  2195.    
  2196.     if ($ReturnExitCode)
  2197.     {
  2198.         return $LastExitCode
  2199.     }
  2200. }
  2201.  
  2202. Function Write-Log([String]$Level, [String]$Message, [Bool]$AppendNewLine = $False, [switch]$NoOutput)
  2203. {
  2204.     $LogMessage = $Message
  2205.     if ($AppendNewLine)
  2206.     {
  2207.         $LogMessage += "`n"
  2208.     }
  2209.     Write-Output -InputObject "$(Get-Date) $LogMessage" | Out-File -FilePath $Script:LogFile -Append
  2210.  
  2211.     if (-not $NoOutput)
  2212.     {
  2213.         switch ($Level)
  2214.         {
  2215.             { $_ -eq $LEVEL_WARNING } { Write-Warning -Message $Message }
  2216.             { $_ -eq $LEVEL_VERBOSE } { Write-Verbose -Message $Message }
  2217.             { $_ -eq $LEVEL_OUTPUT } { Write-Output -InputObject $Message }
  2218.         }
  2219.     }
  2220. }
  2221.  
  2222. Function Verify-MSVS14U1Installed()
  2223. {
  2224.     if (-not (Test-Path -Path "HKLM:\SOFTWARE\Microsoft\DevDiv\VC\Servicing\14.0") -or
  2225.         -not (Test-Path -Path $Script:VSDebuggerBinariesPath) -or
  2226.         -not (Test-Path -Path $Script:VSCRTReleaseBinariesPath) -or
  2227.         -not (Test-Path -Path $Script:VSCRTDebugBinariesPath) -or
  2228.         -not (Test-Path -Path $Script:VSUCRTBinariesPath))
  2229.     {
  2230.         Throw $Strings.ERR_MSVS_REQUIRED
  2231.     }
  2232. }
  2233.  
  2234. Function Verify-UserIsAdministrator()
  2235. {
  2236.     $CurrentUser = New-Object -TypeName Security.Principal.WindowsPrincipal -ArgumentList $([Security.Principal.WindowsIdentity]::GetCurrent())
  2237.     if (!$CurrentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator))
  2238.     {
  2239.         Throw $Strings.ERR_USER_MUST_BE_ADMINISTRATOR
  2240.     }
  2241. }
  2242.  
  2243. Function New-DynamicParameter([String]$Name, [System.Type]$Type, $Value, [switch]$Mandatory, [switch]$NotNull)
  2244. {
  2245.     $ParamAttr = New-Object -TypeName System.Management.Automation.ParameterAttribute
  2246.     $ParamAttr.ParameterSetName  = "__AllParameterSets"
  2247.     $ParamAttr.Mandatory = $Mandatory
  2248.  
  2249.     $AttrCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
  2250.     $AttrCollection.Add($ParamAttr)
  2251.  
  2252.     $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList ($Name, $Type, $AttrCollection)
  2253.  
  2254.     if ($Value)
  2255.     {
  2256.         $Parameter.Value = $Value
  2257.     }
  2258.  
  2259.     return $Parameter
  2260. }
  2261.  
  2262. Function Write-LogHeader()
  2263. {
  2264.     Write-Log $LEVEL_NONE "========================================"
  2265.     Write-Log $LEVEL_NONE ($Strings.LOG_HEADER -f $PSCmdlet.MyInvocation.MyCommand.Name)
  2266.     Write-Log $LEVEL_NONE "========================================"
  2267.  
  2268.     $Params = ($PSCmdlet.MyInvocation.BoundParameters.GetEnumerator() | ForEach-Object {
  2269.         "-{0}:{1}" -f $_.Key, $_.Value
  2270.     }) -Join " "
  2271.  
  2272.     Write-Log $LEVEL_NONE ("{0} {1}" -f $PSCmdlet.MyInvocation.MyCommand.Name, $Params)
  2273. }
  2274.  
  2275. Export-ModuleMember -Function New-NanoServerImage, Edit-NanoServerImage, Get-NanoServerPackage
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement