public/Install-OSDeployMDT.ps1
|
function Install-OSDeployMDT { <# .SYNOPSIS Initializes an MDT Deployment Share for use with OSDeployMDT. .DESCRIPTION Configures an MDT Deployment Share so that Invoke-OSDeployMDT runs automatically on every Update Deployment Share operation. Without -Force, the function runs in audit mode only — it reports the current state of the deployment share without making any changes. With -Force, the function performs the following initialization steps: 1. Resolves the MDT installation directory (INSTALLDIR) and active Deployment Share (DEPLOYROOT). 2. Creates Templates\, Templates\winpe-drivers\, and Templates\winpe-extrafiles\ under DEPLOYROOT. 3. Copies winpeshl.ini, Wimscript.ini, Unattend_PE_x64.xml, and LiteTouchPE.xml from INSTALLDIR\Templates\ to DEPLOYROOT\Templates\. 4. Copies Background.bmp from INSTALLDIR\Samples\ to DEPLOYROOT\Templates\background.bmp. 5. Rewrites %INSTALLDIR% -> %DEPLOYROOT% references in LiteTouchPE.xml. 6. Mirrors the <Components> element from DEPLOYROOT\Boot\LiteTouchPE_x64.xml into the template (or writes a default component list if the Boot file does not exist). 7. Registers the Invoke-OSDeployMDT exit command in LiteTouchPE.xml. 8. Updates Control\Settings.xml: disables x86, sets scratch space to 512 MB, sets wallpaper path. Supports -WhatIf and -Confirm. .PARAMETER Force Performs all initialization changes (creates directories, copies files, modifies XML and Settings.xml). Without this switch the function runs in audit mode and only reports the current state of the deployment share. .EXAMPLE Install-OSDeployMDT Selects the active deployment share, resolves INSTALLDIR, then creates DEPLOYROOT\Templates and copies LiteTouchPE.xml. .EXAMPLE Install-OSDeployMDT -Force Same as above, but overwrites any template and background files that already exist in DEPLOYROOT\Templates. .EXAMPLE Install-OSDeployMDT -WhatIf Shows what initialization actions would be performed without making any changes. .INPUTS None. This function does not accept pipeline input. .OUTPUTS None. .NOTES Author: David Segura Company: Recast Software #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([void])] param ( [Parameter()] [switch] $Force ) Write-OSDeployBanner Write-Host "[$(Get-Date -format s)] Install-OSDeployMDT" -ForegroundColor DarkCyan #region Require Administrator rights $currentPrincipal = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw "[$(Get-Date -format s)] requires Administrator rights. Please run as Administrator." } #endregion #region Resolve MDT installation directory $installDir = Get-MDTInstallDir if ($null -eq $installDir) { Write-Warning "[$(Get-Date -format s)] MDT installation directory (INSTALLDIR) could not be resolved. Aborting." return } Write-Host "[$(Get-Date -format s)] INSTALLDIR '$installDir'" -ForegroundColor DarkGray #endregion #region Select the active deployment share $share = Select-MdtDeploymentShare if ($null -eq $share) { Write-Warning "[$(Get-Date -format s)] No deployment share selected. Aborting." return } #endregion $deployRoot = $share.Path if (-not $PSCmdlet.ShouldProcess($deployRoot, 'Initialize MDT Deployment Share')) { return } Write-Host "[$(Get-Date -format s)] Initializing MDT DeployRoot $deployRoot" -ForegroundColor DarkCyan #region Ensure Templates folder structure exists $templateFolders = @( (Join-Path -Path $deployRoot -ChildPath 'Templates') (Join-Path -Path $deployRoot -ChildPath 'Templates\winpe-drivers') (Join-Path -Path $deployRoot -ChildPath 'Templates\winpe-extrafiles') ) foreach ($folder in $templateFolders) { if (-not (Test-Path -LiteralPath $folder)) { if ($Force) { New-Item -Path $folder -ItemType Directory -Force | Out-Null Write-Host "[$(Get-Date -format s)] [NEW] $folder" -ForegroundColor DarkGreen } else { Write-Host "[$(Get-Date -format s)] [SKIP] $folder (use -Force to create)" -ForegroundColor DarkYellow } } else { Write-Host "[$(Get-Date -format s)] [OK] $folder" -ForegroundColor DarkGray } } $templatesFolder = $templateFolders[0] #endregion #region Copy template files from INSTALLDIR\Templates to DEPLOYROOT\Templates Write-Host "[$(Get-Date -format s)] Copy files from $installDir\Templates" -ForegroundColor DarkCyan $templateCopyMap = @( [ordered]@{ SourceName = 'winpeshl.ini'; DestinationName = 'winpeshl.ini' } [ordered]@{ SourceName = 'Wimscript.ini'; DestinationName = 'wimscript.ini' } [ordered]@{ SourceName = 'Unattend_PE_x64.xml'; DestinationName = 'Unattend_PE_x64.xml' } [ordered]@{ SourceName = 'LiteTouchPE.xml'; DestinationName = 'LiteTouchPE.xml' } ) foreach ($copyItem in $templateCopyMap) { $sourcePath = Join-Path -Path $installDir -ChildPath (Join-Path -Path 'Templates' -ChildPath $copyItem.SourceName) $destPath = Join-Path -Path $templatesFolder -ChildPath $copyItem.DestinationName if (Test-Path -LiteralPath $sourcePath) { if ($Force) { Copy-Item -LiteralPath $sourcePath -Destination $destPath -Force Write-Host "[$(Get-Date -format s)] [COPY] $destPath" -ForegroundColor DarkGreen } elseif (-not (Test-Path -LiteralPath $destPath)) { Write-Host "[$(Get-Date -format s)] [SKIP] $destPath (use -Force to copy)" -ForegroundColor DarkYellow } else { Write-Host "[$(Get-Date -format s)] [OK] $destPath" -ForegroundColor DarkGray } } else { Write-Warning "[$(Get-Date -format s)] Source file not found: '$sourcePath'" } } $destXml = Join-Path -Path $templatesFolder -ChildPath 'LiteTouchPE.xml' #endregion #region Copy Background.bmp from INSTALLDIR\Samples to DEPLOYROOT\Templates (no overwrite) Write-Host "[$(Get-Date -format s)] Copy files from $installDir\Samples" -ForegroundColor DarkCyan $sourceBackground = Join-Path -Path $installDir -ChildPath 'Samples\Background.bmp' $destBackground = Join-Path -Path $templatesFolder -ChildPath 'background.bmp' if (Test-Path -Path $sourceBackground) { if ($Force) { Copy-Item -Path $sourceBackground -Destination $destBackground -Force Write-Host "[$(Get-Date -format s)] [COPY] $destBackground" -ForegroundColor DarkGreen } elseif (-not (Test-Path -Path $destBackground)) { Write-Host "[$(Get-Date -format s)] [SKIP] $destBackground (use -Force to copy)" -ForegroundColor DarkYellow } else { Write-Host "[$(Get-Date -format s)] [OK] $destBackground" -ForegroundColor DarkGray } } else { Write-Warning "[$(Get-Date -format s)] Source file not found: '$sourceBackground'" } #endregion Write-Host "[$(Get-Date -format s)] Update $destXml" -ForegroundColor DarkCyan #region Rewrite INSTALLDIR -> DEPLOYROOT for Unattend and winpeshl Copy sources in LiteTouchPE.xml # Write-Host "[$(Get-Date -format s)] Update INSTALLDIR to DEPLOYROOT in $destXml" -ForegroundColor DarkCyan if (Test-Path -Path $destXml) { try { $xmlContent = Get-Content -Path $destXml -Raw $changed = $false $replacements = @( @{ Find = '%INSTALLDIR%\Templates\Unattend_PE_%PLATFORM%.xml'; Replace = '%DEPLOYROOT%\Templates\Unattend_PE_%PLATFORM%.xml' } @{ Find = '%INSTALLDIR%\Templates\winpeshl.ini'; Replace = '%DEPLOYROOT%\Templates\winpeshl.ini' } ) foreach ($r in $replacements) { if ($xmlContent -match [regex]::Escape($r.Find)) { if ($Force) { $xmlContent = $xmlContent -replace [regex]::Escape($r.Find), $r.Replace Write-Host "[$(Get-Date -format s)] [UPDATE] $($r.Replace)" -ForegroundColor DarkGreen $changed = $true } else { Write-Host "[$(Get-Date -format s)] [SKIP] $($r.Replace) (use -Force to update)" -ForegroundColor DarkYellow } } else { Write-Host "[$(Get-Date -format s)] [OK] $($r.Replace)" -ForegroundColor DarkGray } } if ($changed) { Set-Content -Path $destXml -Value $xmlContent -Encoding UTF8 -NoNewline } } catch { Write-Warning "[$(Get-Date -format s)] Failed to update Copy sources in '$destXml': $_" } } #endregion #region Mirror Components from LiteTouchPE_x64.xml into Templates\LiteTouchPE.xml # Write-Host "[$(Get-Date -format s)] Update WindowsPE Components in $destXml" -ForegroundColor DarkCyan $bootXmlPath = Join-Path -Path $deployRoot -ChildPath 'Boot\LiteTouchPE_x64.xml' if ((Test-Path -Path $bootXmlPath) -and (Test-Path -Path $destXml)) { Write-Verbose "[$(Get-Date -format s)] Merging Components from $bootXmlPath" try { $bootXml = New-Object -TypeName System.Xml.XmlDocument $bootXml.Load($bootXmlPath) $templateXml = New-Object -TypeName System.Xml.XmlDocument $templateXml.Load($destXml) $bootComponents = $bootXml.SelectSingleNode('/Definition/WindowsPE/Components') if ($null -ne $bootComponents) { $templateComponents = $templateXml.SelectSingleNode('/Definition/WindowsPE/Components') # Import the node from the boot XML document into the template document $importedNode = $templateXml.ImportNode($bootComponents, $true) if ($null -ne $templateComponents) { # Replace existing Components node $templateComponents.ParentNode.ReplaceChild($importedNode, $templateComponents) | Out-Null } else { # Append Components under WindowsPE if not present $windowsPE = $templateXml.SelectSingleNode('/Definition/WindowsPE') if ($null -ne $windowsPE) { $windowsPE.AppendChild($importedNode) | Out-Null } } if ($Force) { $templateXml.Save($destXml) Write-Host "[$(Get-Date -format s)] [MERGE] Existing WindowsPE Components from Boot\LiteTouchPE_x64.xml" -ForegroundColor DarkGreen } else { Write-Host "[$(Get-Date -format s)] [SKIP] WindowsPE Components from Boot\LiteTouchPE_x64.xml (use -Force to merge)" -ForegroundColor DarkYellow } } else { Write-Host "[$(Get-Date -format s)] [OK] No Components element found in Boot\LiteTouchPE_x64.xml" -ForegroundColor DarkGray } } catch { Write-Warning "[$(Get-Date -format s)] Failed to merge Components from LiteTouchPE_x64.xml: $_" } } elseif (-not (Test-Path -Path $bootXmlPath)) { # Write-Host "[$(Get-Date -format s)] '$bootXmlPath' not found - writing default Components" -ForegroundColor DarkGray if (Test-Path -Path $destXml) { try { $defaultComponents = @( 'winpe-scripting' 'winpe-hta' 'winpe-wmi' 'winpe-securestartup' 'winpe-fmapi' 'winpe-dismcmdlets' 'winpe-dot3svc' 'winpe-enhancedstorage' 'winpe-hsp-driver' 'winpe-netfx' 'winpe-platformid' 'winpe-pmemcmdlets' 'winpe-powershell' 'winpe-pppoe' 'winpe-rndis' 'winpe-securebootcmdlets' 'winpe-storagewmi' 'winpe-wds-tools' ) $templateXml = New-Object -TypeName System.Xml.XmlDocument $templateXml.Load($destXml) # Locate or create the Components node under /Definition/WindowsPE $templateComponents = $templateXml.SelectSingleNode('/Definition/WindowsPE/Components') if ($null -eq $templateComponents) { $windowsPE = $templateXml.SelectSingleNode('/Definition/WindowsPE') if ($null -ne $windowsPE) { $templateComponents = $templateXml.CreateElement('Components') $windowsPE.AppendChild($templateComponents) | Out-Null } } if ($null -ne $templateComponents) { # Remove any existing Component children before writing defaults $templateComponents.RemoveAll() foreach ($name in $defaultComponents) { $componentNode = $templateXml.CreateElement('Component') $componentNode.InnerText = $name $templateComponents.AppendChild($componentNode) | Out-Null } if ($Force) { $templateXml.Save($destXml) Write-Host "[$(Get-Date -format s)] [UPDATE] WindowsPE Components updated in $destXml" -ForegroundColor DarkGreen } else { Write-Host "[$(Get-Date -format s)] [SKIP] WindowsPE default Components (use -Force to update)" -ForegroundColor DarkYellow } } } catch { Write-Warning "[$(Get-Date -format s)] Failed to update default Components to template: $_" } } } #endregion #region Ensure Invoke-OSDeployMDT Exit entry exists in LiteTouchPE.xml # Write-Host "[$(Get-Date -format s)] Update Exits in $destXml" -ForegroundColor DarkCyan $buildMdtExit = 'start /wait pwsh.exe -ExecutionPolicy Bypass -Command "Invoke-OSDeployMDT"' if (Test-Path -Path $destXml) { try { $exitXml = New-Object -TypeName System.Xml.XmlDocument $exitXml.Load($destXml) $exitsNode = $exitXml.SelectSingleNode('/Definition/WindowsPE/Exits') if ($null -eq $exitsNode) { $windowsPE = $exitXml.SelectSingleNode('/Definition/WindowsPE') if ($null -ne $windowsPE) { $exitsNode = $exitXml.CreateElement('Exits') $windowsPE.AppendChild($exitsNode) | Out-Null } } if ($null -ne $exitsNode) { # Check if the entry already exists $existing = $exitsNode.SelectSingleNode("Exit[. = '$buildMdtExit']") if ($null -eq $existing) { if ($Force) { $exitNode = $exitXml.CreateElement('Exit') $exitNode.InnerText = $buildMdtExit $exitsNode.AppendChild($exitNode) | Out-Null $exitXml.Save($destXml) Write-Host "[$(Get-Date -format s)] [ADD] start /wait pwsh.exe -ExecutionPolicy Bypass -Command `"Invoke-OSDeployMDT`"" -ForegroundColor DarkGreen } else { Write-Host "[$(Get-Date -format s)] [SKIP] Invoke-OSDeployMDT Exit entry (use -Force to add)" -ForegroundColor DarkYellow } } else { Write-Host "[$(Get-Date -format s)] [OK] start /wait pwsh.exe -ExecutionPolicy Bypass -Command `"Invoke-OSDeployMDT`"" -ForegroundColor DarkGray } } } catch { Write-Warning "[$(Get-Date -format s)] Failed to update Exits in '$destXml': $_" } } #endregion #region Update DEPLOYROOT\Control\Settings.xml $settingsXmlPath = Join-Path -Path $deployRoot -ChildPath 'Control\Settings.xml' if (Test-Path -Path $settingsXmlPath) { #region Backup Settings.xml (no overwrite) $settingsBackupPath = Join-Path -Path $deployRoot -ChildPath 'Control\Settings.xml.backup' if (-not (Test-Path -Path $settingsBackupPath)) { if ($Force) { Copy-Item -Path $settingsXmlPath -Destination $settingsBackupPath Write-Host "[$(Get-Date -format s)] [BACKUP] $settingsBackupPath" -ForegroundColor DarkGreen } else { Write-Host "[$(Get-Date -format s)] [SKIP] $settingsBackupPath (use -Force to create)" -ForegroundColor DarkYellow } } else { Write-Host "[$(Get-Date -format s)] [OK] $settingsBackupPath" -ForegroundColor DarkGray } #endregion Write-Host "[$(Get-Date -format s)] Update x64 Settings in $settingsXmlPath" -ForegroundColor DarkCyan try { $settingsContent = Get-Content -Path $settingsXmlPath -Raw $changed = $false $replacements = @( @{ Find = '%INSTALLDIR%\Samples\Background.bmp'; Replace = '%DEPLOYROOT%\Templates\background.bmp'; IsRegex = $false } @{ Find = '(?s)<Boot\.x64\.ExtraDirectory>\s*</Boot\.x64\.ExtraDirectory>'; Replace = '<Boot.x64.ExtraDirectory>%DEPLOYROOT%\Templates\winpe-extrafiles</Boot.x64.ExtraDirectory>'; IsRegex = $true; ExistsPattern = '<Boot\.x64\.ExtraDirectory>' } @{ Find = '<Boot.x64.ScratchSpace>32</Boot.x64.ScratchSpace>'; Replace = '<Boot.x64.ScratchSpace>512</Boot.x64.ScratchSpace>'; IsRegex = $false } ) foreach ($r in $replacements) { $pattern = if ($r.IsRegex) { $r.Find } else { [regex]::Escape($r.Find) } if ($settingsContent -match $pattern) { if ($Force) { $settingsContent = $settingsContent -replace $pattern, $r.Replace Write-Host "[$(Get-Date -format s)] [UPDATE] $($r.Replace)" -ForegroundColor DarkGreen $changed = $true } else { Write-Host "[$(Get-Date -format s)] [SKIP] $($r.Replace) (use -Force to update)" -ForegroundColor DarkYellow } } else { if ($r.ExistsPattern -and ($settingsContent -match $r.ExistsPattern)) { Write-Host "[$(Get-Date -format s)] Not modifying '$($r.Find)' - already has a value" -ForegroundColor DarkGray } else { Write-Host "[$(Get-Date -format s)] [OK] $($r.Replace)" -ForegroundColor DarkGray } } } if ($changed) { Set-Content -Path $settingsXmlPath -Value $settingsContent -Encoding UTF8 -NoNewline } } catch { Write-Warning "[$(Get-Date -format s)] Failed to update '$settingsXmlPath': $_" } Write-Host "[$(Get-Date -format s)] Disable x86 Settings in $settingsXmlPath" -ForegroundColor DarkCyan try { $settingsContent = Get-Content -Path $settingsXmlPath -Raw $changed = $false $replacements = @( @{ Find = '<SupportX86>True</SupportX86>'; Replace = '<SupportX86>False</SupportX86>'; IsRegex = $false } @{ Find = '<Boot.x86.UseBootWim>True</Boot.x86.UseBootWim>'; Replace = '<Boot.x86.UseBootWim>False</Boot.x86.UseBootWim>'; IsRegex = $false } @{ Find = '<Boot.x86.IncludeAllDrivers>True</Boot.x86.IncludeAllDrivers>'; Replace = '<Boot.x86.IncludeAllDrivers>False</Boot.x86.IncludeAllDrivers>'; IsRegex = $false } @{ Find = '<Boot.x86.IncludeNetworkDrivers>True</Boot.x86.IncludeNetworkDrivers>'; Replace = '<Boot.x86.IncludeNetworkDrivers>False</Boot.x86.IncludeNetworkDrivers>';IsRegex = $false } @{ Find = '<Boot.x86.IncludeMassStorageDrivers>True</Boot.x86.IncludeMassStorageDrivers>';Replace = '<Boot.x86.IncludeMassStorageDrivers>False</Boot.x86.IncludeMassStorageDrivers>';IsRegex = $false } @{ Find = '<Boot.x86.IncludeVideoDrivers>True</Boot.x86.IncludeVideoDrivers>'; Replace = '<Boot.x86.IncludeVideoDrivers>False</Boot.x86.IncludeVideoDrivers>'; IsRegex = $false } @{ Find = '<Boot.x86.IncludeSystemDrivers>True</Boot.x86.IncludeSystemDrivers>'; Replace = '<Boot.x86.IncludeSystemDrivers>False</Boot.x86.IncludeSystemDrivers>'; IsRegex = $false } @{ Find = '<Boot.x86.GenerateLiteTouchISO>True</Boot.x86.GenerateLiteTouchISO>'; Replace = '<Boot.x86.GenerateLiteTouchISO>False</Boot.x86.GenerateLiteTouchISO>'; IsRegex = $false } ) foreach ($r in $replacements) { $pattern = if ($r.IsRegex) { $r.Find } else { [regex]::Escape($r.Find) } if ($settingsContent -match $pattern) { if ($Force) { $settingsContent = $settingsContent -replace $pattern, $r.Replace Write-Host "[$(Get-Date -format s)] [DISABLE] $($r.Replace)" -ForegroundColor DarkGray $changed = $true } else { Write-Host "[$(Get-Date -format s)] [SKIP] $($r.Replace) (use -Force to disable)" -ForegroundColor DarkGray } } else { if ($r.ExistsPattern -and ($settingsContent -match $r.ExistsPattern)) { Write-Host "[$(Get-Date -format s)] Not modifying '$($r.Find)' - already has a value" -ForegroundColor DarkGray } else { Write-Host "[$(Get-Date -format s)] [OK] $($r.Replace)" -ForegroundColor DarkGray } } } if ($changed) { Set-Content -Path $settingsXmlPath -Value $settingsContent -Encoding UTF8 -NoNewline } } catch { Write-Warning "[$(Get-Date -format s)] Failed to update '$settingsXmlPath': $_" } } else { Write-Host "[$(Get-Date -format s)] Settings.xml not found" -ForegroundColor DarkGray } #endregion if ($Force) { Write-Host Write-Host "🚗 WinPE Drivers can be added to $templatesFolder\winpe-drivers" -ForegroundColor DarkCyan Write-Host "➕ WinPE ExtraFiles can be added to $templatesFolder\winpe-extrafiles" -ForegroundColor DarkCyan Write-Host "🧱 Wallpaper can be customized by replacing $destBackground" -ForegroundColor DarkCyan Write-Host "⚙️ Invoke-OSDeployMDT will run when running Update Deployment Share" -ForegroundColor DarkCyan Write-Host "🎉 PowerShell Gallery will now work in your bootimage" -ForegroundColor DarkCyan Write-Host "☁️ OSDCloud will now work in your bootimage by running Deploy-OSDCloud in PowerShell" -ForegroundColor DarkCyan } } |