Private/Apply-OrgTemplate.ps1
|
function Apply-OrgTemplate { [CmdletBinding()] param( [string]$ProjectPath, [PSCustomObject]$Template ) try { # Set-TextBlock (replace first regex match via index maths; -Label warns on a miss = PSADT # template drift) lives in Private\Set-Win32ToolkitTextBlock.ps1, dot-sourced by the loader. # Scope note: the config.psd1 patches below use broad `-replace` patterns and are deliberately # not label-verified here. # Escape single quotes so a free-text template value (company name, dialog messages, log path, # author) stays a valid single-quoted literal in the generated config.psd1 / strings.psd1 / # deploy script. Used everywhere a value is spliced into a '...' literal below. [string]$null -> ''. function Esc { param([string]$s) $s -replace "'", "''" } #── config.psd1 (data-driven, F1) ────────────────────────────────── # REPLACE the scaffolded full config.psd1 with a SPARSE file carrying only the template's # deltas. PSADT superimposes it over its signed module default key-by-key, so every omitted # key keeps the default — no more regex-patching a full copy (which drifted on PSADT text # changes). New config knobs are now one hashtable entry in New-Win32ToolkitSparseConfig. $configPath = Join-Path $ProjectPath 'Config\config.psd1' if (Test-Path $configPath) { $cfg = New-Win32ToolkitSparseConfig -Template $Template # UTF-8 WITH BOM — read on-device by Windows PowerShell 5.1 (Import-PowerShellDataFile), # which decodes a BOM-less file as ANSI and would mojibake a non-ASCII CompanyName. PS7's # Set-Content -Encoding UTF8 writes NO BOM, so write the bytes ourselves. [System.IO.File]::WriteAllText($configPath, $cfg, (New-Object System.Text.UTF8Encoding($true))) Write-Verbose ' config.psd1 replaced with sparse template config' } #── strings.psd1 ─────────────────────────────────────────────────── $strPath = Join-Path $ProjectPath 'Strings\strings.psd1' if (Test-Path $strPath) { $str = Get-Content $strPath -Raw -Encoding UTF8 # BalloonTip.Complete sub-block $newBalloon = " # Text displayed in the balloon tip for successful completion of a deployment type.`r`n Complete = @{`r`n Install = '$(Esc $Template.BalloonComplete.Install)'`r`n Repair = '$(Esc $Template.BalloonComplete.Repair)'`r`n Uninstall = '$(Esc $Template.BalloonComplete.Uninstall)'`r`n }" $str = Set-TextBlock -Text $str ` -Pattern '(?s) # Text displayed in the balloon tip for successful completion of a deployment type\.\r?\n Complete = @\{[^}]*\}' ` -Replacement $newBalloon -Multiline -Label 'balloon-complete messages' # ProgressPrompt — entire block up to RestartPrompt $nl = if ($str -match '\r\n') { "`r`n" } else { "`n" } $newProg = " ProgressPrompt = @{${nl} # Default message displayed in the progress bar.${nl} Message = @{${nl} Install = '$(Esc $Template.ProgressMessage.Install)'${nl} Repair = '$(Esc $Template.ProgressMessage.Repair)'${nl} Uninstall = '$(Esc $Template.ProgressMessage.Uninstall)'${nl} }${nl}${nl} # Default message detail displayed in the progress bar.${nl} MessageDetail = @{${nl} Install = '$(Esc $Template.ProgressMessageDetail.Install)'${nl} Repair = '$(Esc $Template.ProgressMessageDetail.Repair)'${nl} Uninstall = '$(Esc $Template.ProgressMessageDetail.Uninstall)'${nl} }${nl}${nl} # The subtitle underneath the Install Title, e.g. Company Name. Only for Fluent dialogs.${nl} Subtitle = @{${nl} Install = '{Toolkit\CompanyName} - App Installation'${nl} Repair = '{Toolkit\CompanyName} - App Repair'${nl} Uninstall = '{Toolkit\CompanyName} - App Uninstallation'${nl} }${nl} }" $str = Set-TextBlock -Text $str ` -Pattern '(?s) ProgressPrompt = @\{.*?(?=\r?\n RestartPrompt)' ` -Replacement $newProg -Multiline -Label 'progress-prompt messages' # UTF-8 WITH BOM (see config.psd1 above) — dialog strings are the most likely place for # non-ASCII text (accents, curly quotes, ™/®), and they render on the user's screen. [System.IO.File]::WriteAllText($strPath, $str, (New-Object System.Text.UTF8Encoding($true))) Write-Verbose ' strings.psd1 updated' } #── Invoke-AppDeployToolkit.ps1 ───────────────────────────────────── $scriptPath = Join-Path $ProjectPath 'Invoke-AppDeployToolkit.ps1' if (Test-Path $scriptPath) { $scr = Get-Content $scriptPath -Raw -Encoding UTF8 # AppScriptAuthor — escape single quotes so a free-text author like "O'Brien IT" stays a # valid single-quoted literal. This value now drives the on-device install tattoo and the # Intune detection key (see Set-PSADTDataDrivenScript / Get-Win32DetectionRules), so a # broken literal here would fail the whole deploy script on the device. Set-TextBlock does # literal insertion (index math), so a '$' in the author is never treated as a backreference. $authorLiteral = "AppScriptAuthor = '" + ($Template.AppScriptAuthor -replace "'", "''") + "'" $scr = Set-TextBlock -Text $scr -Pattern "AppScriptAuthor = '[^']*'" -Replacement $authorLiteral -Label 'AppScriptAuthor' # ── Install Welcome dialog ── if ($Template.WelcomeDialog.Enabled) { $saiwLines = [System.Collections.Generic.List[string]]::new() # base hashtable entries $saiwCloseLines = [System.Collections.Generic.List[string]]::new() # only valid with CloseProcesses if ($Template.WelcomeDialog.AllowDefer) { $saiwLines.Add(" AllowDefer = `$true") $saiwLines.Add(" DeferTimes = $($Template.WelcomeDialog.DeferTimes)") } if ($Template.WelcomeDialog.CheckDiskSpace) { $saiwLines.Add(" CheckDiskSpace = `$true") } if ($Template.WelcomeDialog.PersistPrompt) { $saiwLines.Add(" PersistPrompt = `$true") } if ($Template.WelcomeDialog.CustomText) { # Only emit CustomText if strings.psd1 actually has a non-empty CustomMessage $strPathForCheck = Join-Path $ProjectPath 'Strings\strings.psd1' $hasCustomMsg = (Test-Path $strPathForCheck) -and ((Get-Content $strPathForCheck -Raw) -match "CustomMessage\s*=\s*'[^']+'") if ($hasCustomMsg) { $saiwLines.Add(" CustomText = `$true") } } # BlockExecution and CloseProcessesCountdown only belong to 'with processes to close' parameter sets if ($Template.WelcomeDialog.BlockExecution) { $saiwCloseLines.Add(" `$saiwParams.Add('BlockExecution', `$true)") } if ($Template.WelcomeDialog.CloseProcessesCountdown -gt 0) { $saiwCloseLines.Add(" `$saiwParams.Add('CloseProcessesCountdown', $($Template.WelcomeDialog.CloseProcessesCountdown))") } $saiwBody = $saiwLines -join "`n" $saiwCloseBody = if ($saiwCloseLines.Count -gt 0) { "`n" + ($saiwCloseLines -join "`n") } else { '' } $newWelcome = " ## Show Welcome Message, close processes if specified.`n `$saiwParams = @{`n$saiwBody`n }`n if (`$adtSession.AppProcessesToClose.Count -gt 0)`n {`n `$saiwParams.Add('CloseProcesses', `$adtSession.AppProcessesToClose)$saiwCloseBody`n }`n Show-ADTInstallationWelcome @saiwParams" } else { $newWelcome = " ## Welcome dialog disabled by org template." } # Pattern matches both original PSADT format and our re-applied format $scr = Set-TextBlock -Text $scr ` -Pattern '(?s) ## (?:Show Welcome Message[^\n]*\r?\n \$saiwParams = @\{.*? Show-ADTInstallationWelcome @saiwParams|Welcome dialog disabled by org template\.)' ` -Replacement $newWelcome -Multiline -Label 'install welcome dialog' # ── Uninstall Welcome dialog ── $uwSettings = if ($Template.PSObject.Properties['UninstallWelcomeDialog']) { $Template.UninstallWelcomeDialog } else { $null } if ($uwSettings -and $uwSettings.Enabled) { $uArgs = [System.Collections.Generic.List[string]]::new() if ($uwSettings.CloseProcessesCountdown -gt 0) { $uArgs.Add("-CloseProcessesCountdown $($uwSettings.CloseProcessesCountdown)") } if ($uwSettings.PersistPrompt) { $uArgs.Add('-PersistPrompt') } if ($uwSettings.BlockExecution) { $uArgs.Add('-BlockExecution') } $uArgStr = if ($uArgs.Count -gt 0) { ' ' + ($uArgs -join ' ') } else { '' } $newUninstallWelcome = " ## If there are processes to close, show Welcome Message before uninstalling.`n if (`$adtSession.AppProcessesToClose.Count -gt 0)`n {`n Show-ADTInstallationWelcome -CloseProcesses `$adtSession.AppProcessesToClose$uArgStr`n }" } else { $newUninstallWelcome = " ## Uninstall welcome dialog disabled by org template." } $scr = Set-TextBlock -Text $scr ` -Pattern '(?s) ## (?:If there are processes to close.*?\r?\n \}|Uninstall welcome dialog disabled by org template\.)' ` -Replacement $newUninstallWelcome -Multiline -Label 'uninstall welcome dialog' # ── Progress dialog (all 3 occurrences: Install, Uninstall, Repair) ── if ($Template.ProgressDialog.Enabled) { $progressArgs = '' if ($Template.ProgressDialog.StatusMessage -and $Template.ProgressDialog.StatusMessage.Trim() -ne '') { $progressArgs += " -StatusMessage '$(Esc $Template.ProgressDialog.StatusMessage)'" } if ($Template.ProgressDialog.StatusMessageDetail -and $Template.ProgressDialog.StatusMessageDetail.Trim() -ne '') { $progressArgs += " -StatusMessageDetail '$(Esc $Template.ProgressDialog.StatusMessageDetail)'" } $newProgress = " ## Show Progress Message (with the default message).`n Show-ADTInstallationProgress$progressArgs" } else { $newProgress = " ## Progress dialog disabled by org template." } $progPattern = ' ## (?:Show Progress Message[^\n]*\n Show-ADTInstallationProgress[^\n]*|Progress dialog disabled by org template\.)' $offset = 0; $replaced = 0 while ($replaced -lt 5) { $m = [regex]::Match($scr.Substring($offset), $progPattern) if (-not $m.Success) { break } $absIdx = $offset + $m.Index $scr = $scr.Substring(0, $absIdx) + $newProgress + $scr.Substring($absIdx + $m.Length) $offset = $absIdx + $newProgress.Length $replaced++ } if ($replaced -eq 0) { Write-Warning "Org template: 'progress dialog' pattern not found — section skipped (PSADT template drift?)." } # ── Completion prompt (Post-Install) ── if ($Template.CompletionPrompt.Enabled) { $msg = $Template.CompletionPrompt.Message -replace "'", "''" $btn = $Template.CompletionPrompt.ButtonRightText -replace "'", "''" $newCompletion = " ## Display a message at the end of the install.`n if (!`$adtSession.UseDefaultMsi)`n {`n Show-ADTInstallationPrompt -Message '$msg' -ButtonRightText '$btn' -NoWait`n }" } else { $newCompletion = " ## Completion prompt disabled by org template." } $scr = Set-TextBlock -Text $scr ` -Pattern '(?s) ## (?:Display a message at the end[^\n]*\r?\n if \(!\$adtSession\.UseDefaultMsi\)\r?\n \{[\s\S]*?\}|Completion prompt disabled by org template\.)' ` -Replacement $newCompletion -Multiline -Label 'completion prompt' # UTF-8 WITH BOM (see config.psd1 above) — this script is executed on the device by Windows # PowerShell 5.1; a BOM-less non-ASCII AppScriptAuthor/prompt message would mojibake there. [System.IO.File]::WriteAllText($scriptPath, $scr, (New-Object System.Text.UTF8Encoding($true))) Write-Verbose ' Invoke-AppDeployToolkit.ps1 updated' } #── B1/B7: org branding assets (logo + Classic banner) ────────────── # Copies Templates\<name>\Assets\{AppIcon.png, Banner.Classic.png} into the project. The logo is a # precedence-respecting base (winget/manual/captured still win); opt-in via CustomAssets. Add-Win32ToolkitTemplateAssets -ProjectPath $ProjectPath -Template $Template #── A1/A3: org hook scripts + extension module ────────────────────── # Copy-not-splice: operator scripts are copied into SupportFiles\OrgHooks\ (and any org PSADT # extension module into the project root); only a compile-time-constant stub is injected at the # v4 markers. Opt-in per template (Hooks.Enabled / ExtensionModule); a no-op when both are off. Add-Win32ToolkitOrgHooks -ProjectPath $ProjectPath -Template $Template Write-Host "✓ Org template '$($Template.TemplateName)' applied to project" -ForegroundColor Green return $true } catch { Write-Warning "Failed to apply org template: $($_.Exception.Message)" return $false } } |