From 17f78836267a0445d96b6aaac132b67920b8f387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Mon, 15 Jun 2026 17:23:01 +0200 Subject: [PATCH 01/11] chore(os-recovery): open planning branch From 1c7f61a097bf77106eca8ed65100777ce15a3f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Mon, 15 Jun 2026 19:24:30 +0200 Subject: [PATCH 02/11] feat(os-recovery): add WinRE recovery deployment flow --- scripts/Test-FoundryOsRecoveryWinRe.ps1 | 337 +++++++++++++ .../DeployConfigurationGeneratorTests.cs | 26 + .../FoundryConfigurationServiceTests.cs | 5 + ...RecoveryPayloadProvisioningServiceTests.cs | 355 +++++++++++++ .../Assets/WinPe/FoundryBootstrap.ps1 | 39 +- .../Assets/WinRe/FoundryRecoveryLauncher.cmd | 14 + src/Foundry.Core/Foundry.Core.csproj | 3 + .../ConfigurationSchemaVersions.cs | 4 +- .../Deploy/DeployOsRecoverySettings.cs | 12 + .../FoundryDeployConfigurationDocument.cs | 5 + .../FoundryConfigurationDocument.cs | 5 + .../Configuration/OsRecoverySettings.cs | 12 + .../DeployConfigurationGenerator.cs | 4 + .../IOsRecoveryPayloadProvisioningService.cs | 9 + .../OsRecoveryBootMenuLocalization.cs | 8 + .../OsRecoveryPayloadProvisioningOptions.cs | 18 + .../OsRecoveryPayloadProvisioningResult.cs | 7 + .../OsRecoveryPayloadProvisioningService.cs | 385 +++++++++++++++ ...DeploymentLaunchPreparationServiceTests.cs | 41 +- .../DeploymentOrchestratorTests.cs | 8 +- .../DeploymentRuntimeContextServiceTests.cs | 27 + .../DeploymentStepExecutionContextTests.cs | 16 + .../PreOobeNetworkProfileRoamingStepTests.cs | 13 + .../PrepareTargetDiskLayoutStepTests.cs | 13 + .../ProvisionOsRecoveryStepTests.cs | 437 ++++++++++++++++ .../RecoveryTargetDiskResolverTests.cs | 72 +++ .../WindowsDeploymentServiceTests.cs | 46 +- .../ServiceCollectionExtensions.cs | 6 + .../Configuration/DeployOsRecoverySettings.cs | 12 + .../FoundryDeployConfigurationDocument.cs | 5 + src/Foundry.Deploy/Models/DeploymentMode.cs | 3 +- .../Services/Cache/CacheLocatorService.cs | 1 + .../Services/Deployment/DeploymentContext.cs | 5 + .../DeploymentLaunchPreparationService.cs | 22 +- .../Deployment/DeploymentLaunchRequest.cs | 1 + .../Deployment/DeploymentOrchestrator.cs | 2 + .../Deployment/DeploymentRuntimeState.cs | 5 + .../DeploymentStepExecutionContext.cs | 4 +- .../Deployment/DeploymentStepNames.cs | 2 + .../Deployment/IWindowsDeploymentService.cs | 14 + .../RecoveryTargetDiskLayoutMode.cs | 7 + .../Steps/ApplyFirmwareUpdateStep.cs | 2 +- .../Steps/DownloadFirmwareUpdateStep.cs | 2 +- .../FinalizeDeploymentAndWriteLogsStep.cs | 3 +- .../Steps/GatherDeploymentVariablesStep.cs | 5 + .../Steps/PrepareTargetDiskLayoutStep.cs | 5 + .../Steps/ProvisionAutopilotStep.cs | 2 +- .../Steps/ProvisionOsRecoveryStep.cs | 465 ++++++++++++++++++ .../Steps/SealRecoveryPartitionStep.cs | 2 +- .../Deployment/WindowsDeploymentService.cs | 142 ++++++ .../Localization/DeploymentUiTextLocalizer.cs | 9 + .../DeploymentRuntimeContextService.cs | 3 +- .../Runtime/IRecoveryTargetDiskResolver.cs | 6 + .../Runtime/RecoveryTargetDiskResolver.cs | 126 +++++ .../Startup/DeploymentStartupCoordinator.cs | 35 +- .../Wizard/DeploymentWizardContext.cs | 2 + .../Strings/ar-SA/Resources.resx | 21 + .../Strings/bg-BG/Resources.resx | 21 + .../Strings/cs-CZ/Resources.resx | 21 + .../Strings/da-DK/Resources.resx | 21 + .../Strings/de-DE/Resources.resx | 21 + .../Strings/el-GR/Resources.resx | 21 + .../Strings/en-GB/Resources.resx | 21 + .../Strings/en-US/Resources.resx | 21 + .../Strings/es-ES/Resources.resx | 21 + .../Strings/es-MX/Resources.resx | 21 + .../Strings/et-EE/Resources.resx | 21 + .../Strings/fi-FI/Resources.resx | 21 + .../Strings/fr-CA/Resources.resx | 21 + .../Strings/fr-FR/Resources.resx | 21 + .../Strings/he-IL/Resources.resx | 21 + .../Strings/hr-HR/Resources.resx | 21 + .../Strings/hu-HU/Resources.resx | 21 + .../Strings/it-IT/Resources.resx | 21 + .../Strings/ja-JP/Resources.resx | 21 + .../Strings/ko-KR/Resources.resx | 21 + .../Strings/lt-LT/Resources.resx | 21 + .../Strings/lv-LV/Resources.resx | 21 + .../Strings/nb-NO/Resources.resx | 21 + .../Strings/nl-NL/Resources.resx | 21 + .../Strings/pl-PL/Resources.resx | 21 + .../Strings/pt-BR/Resources.resx | 21 + .../Strings/pt-PT/Resources.resx | 21 + .../Strings/ro-RO/Resources.resx | 21 + .../Strings/ru-RU/Resources.resx | 21 + .../Strings/sk-SK/Resources.resx | 21 + .../Strings/sl-SI/Resources.resx | 21 + .../Strings/sr-Latn-RS/Resources.resx | 21 + .../Strings/sv-SE/Resources.resx | 21 + .../Strings/th-TH/Resources.resx | 21 + .../Strings/tr-TR/Resources.resx | 21 + .../Strings/uk-UA/Resources.resx | 21 + .../Strings/zh-CN/Resources.resx | 21 + .../Strings/zh-TW/Resources.resx | 21 + .../ViewModels/MainWindowViewModel.cs | 1 + .../TelemetryBootMediaTargetResolverTests.cs | 2 + .../TelemetryBootMediaTargetResolver.cs | 1 + .../TelemetryBootMediaTargets.cs | 1 + src/Foundry/Assets/NavViewMenu/AppData.json | 10 + .../OsRecovery/winre-os-recovery-flow.png | Bin 0 -> 27381 bytes .../ServiceCollectionExtensions.cs | 1 + .../FoundryConfigurationStateService.cs | 9 + .../IFoundryConfigurationStateService.cs | 6 + src/Foundry/Strings/ar-SA/Resources.resw | 57 +++ src/Foundry/Strings/bg-BG/Resources.resw | 57 +++ src/Foundry/Strings/cs-CZ/Resources.resw | 57 +++ src/Foundry/Strings/da-DK/Resources.resw | 57 +++ src/Foundry/Strings/de-DE/Resources.resw | 57 +++ src/Foundry/Strings/el-GR/Resources.resw | 57 +++ src/Foundry/Strings/en-GB/Resources.resw | 57 +++ src/Foundry/Strings/en-US/Resources.resw | 57 +++ src/Foundry/Strings/es-ES/Resources.resw | 57 +++ src/Foundry/Strings/es-MX/Resources.resw | 57 +++ src/Foundry/Strings/et-EE/Resources.resw | 57 +++ src/Foundry/Strings/fi-FI/Resources.resw | 57 +++ src/Foundry/Strings/fr-CA/Resources.resw | 57 +++ src/Foundry/Strings/fr-FR/Resources.resw | 57 +++ src/Foundry/Strings/he-IL/Resources.resw | 57 +++ src/Foundry/Strings/hr-HR/Resources.resw | 57 +++ src/Foundry/Strings/hu-HU/Resources.resw | 57 +++ src/Foundry/Strings/it-IT/Resources.resw | 57 +++ src/Foundry/Strings/ja-JP/Resources.resw | 57 +++ src/Foundry/Strings/ko-KR/Resources.resw | 57 +++ src/Foundry/Strings/lt-LT/Resources.resw | 57 +++ src/Foundry/Strings/lv-LV/Resources.resw | 57 +++ src/Foundry/Strings/nb-NO/Resources.resw | 57 +++ src/Foundry/Strings/nl-NL/Resources.resw | 57 +++ src/Foundry/Strings/pl-PL/Resources.resw | 57 +++ src/Foundry/Strings/pt-BR/Resources.resw | 57 +++ src/Foundry/Strings/pt-PT/Resources.resw | 57 +++ src/Foundry/Strings/ro-RO/Resources.resw | 57 +++ src/Foundry/Strings/ru-RU/Resources.resw | 57 +++ src/Foundry/Strings/sk-SK/Resources.resw | 57 +++ src/Foundry/Strings/sl-SI/Resources.resw | 57 +++ src/Foundry/Strings/sr-Latn-RS/Resources.resw | 57 +++ src/Foundry/Strings/sv-SE/Resources.resw | 57 +++ src/Foundry/Strings/th-TH/Resources.resw | 57 +++ src/Foundry/Strings/tr-TR/Resources.resw | 57 +++ src/Foundry/Strings/uk-UA/Resources.resw | 57 +++ src/Foundry/Strings/zh-CN/Resources.resw | 57 +++ src/Foundry/Strings/zh-TW/Resources.resw | 57 +++ .../OsRecoveryConfigurationViewModel.cs | 137 ++++++ src/Foundry/Views/OsRecoveryPage.xaml | 70 +++ src/Foundry/Views/OsRecoveryPage.xaml.cs | 19 + 144 files changed, 6011 insertions(+), 29 deletions(-) create mode 100644 scripts/Test-FoundryOsRecoveryWinRe.ps1 create mode 100644 src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs create mode 100644 src/Foundry.Core/Assets/WinRe/FoundryRecoveryLauncher.cmd create mode 100644 src/Foundry.Core/Models/Configuration/Deploy/DeployOsRecoverySettings.cs create mode 100644 src/Foundry.Core/Models/Configuration/OsRecoverySettings.cs create mode 100644 src/Foundry.Core/Services/WinPe/OsRecovery/IOsRecoveryPayloadProvisioningService.cs create mode 100644 src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryBootMenuLocalization.cs create mode 100644 src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs create mode 100644 src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningResult.cs create mode 100644 src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs create mode 100644 src/Foundry.Deploy.Tests/DeploymentRuntimeContextServiceTests.cs create mode 100644 src/Foundry.Deploy.Tests/ProvisionOsRecoveryStepTests.cs create mode 100644 src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs create mode 100644 src/Foundry.Deploy/Models/Configuration/DeployOsRecoverySettings.cs create mode 100644 src/Foundry.Deploy/Services/Deployment/RecoveryTargetDiskLayoutMode.cs create mode 100644 src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs create mode 100644 src/Foundry.Deploy/Services/Runtime/IRecoveryTargetDiskResolver.cs create mode 100644 src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs create mode 100644 src/Foundry/Assets/OsRecovery/winre-os-recovery-flow.png create mode 100644 src/Foundry/ViewModels/OsRecoveryConfigurationViewModel.cs create mode 100644 src/Foundry/Views/OsRecoveryPage.xaml create mode 100644 src/Foundry/Views/OsRecoveryPage.xaml.cs diff --git a/scripts/Test-FoundryOsRecoveryWinRe.ps1 b/scripts/Test-FoundryOsRecoveryWinRe.ps1 new file mode 100644 index 00000000..79b2a682 --- /dev/null +++ b/scripts/Test-FoundryOsRecoveryWinRe.ps1 @@ -0,0 +1,337 @@ +<# +.SYNOPSIS +Validate a mounted or extracted WinRE image for Foundry OS Recovery readiness. + +.DESCRIPTION +This script inspects a WinRE working tree for required recovery artifacts and +validates non-recoverable exclusions. Validation is non-destructive by default. +Use `-BootToRecovery` with `-Force` to request a one-time reboot path. + +.PARAMETER WinReRoot +Path to the mounted/extracted WinRE image root. +#> +[CmdletBinding(SupportsShouldProcess = $true)] +param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$WinReRoot, + + [Parameter()] + [string[]]$LauncherCandidates = @( + 'Sources\Recovery\Tools\FoundryRecoveryLauncher.cmd', + 'FoundryRecoveryLauncher.cmd', + 'FoundryRecovery.exe', + 'FoundryRecoveryLauncher.exe', + 'Foundry.RecoveryLauncher.exe', + 'Foundry.Recovery.Tool.exe', + 'Foundry.WinRE.Launcher.exe' + ), + + [Parameter()] + [string]$WinReConfigFile = 'WinREConfig.xml', + + [Parameter()] + [string]$BootstrapScript = 'FoundryBootstrap.ps1', + + [Parameter()] + [string[]]$ExcludedConfigPatterns = @( + 'Foundry\\Config\\foundry.deployment.config.json', + 'Foundry\\Config\\foundry.connect.provisioning-source.txt', + 'Foundry\\Config\\foundry.deploy.provisioning-source.txt', + 'Foundry\\Config\\Secrets\\*', + 'Foundry\\Config\\Network\\*', + 'Foundry\\Config\\Autopilot\\*', + 'Foundry\\Runtime\\AutopilotHash\\*', + 'Foundry\\Tools\\OA3\\*', + 'Foundry\\Config\\*enterprise*.*', + 'Foundry\\Config\\*personalization*.*' + ), + + [Parameter()] + [switch]$BootToRecovery, + + [Parameter()] + [switch]$SkipReAgentC, + + [Parameter()] + [switch]$Force +) + +Set-StrictMode -Version Latest + +function Resolve-AbsolutePath { + param( + [Parameter(Mandatory = $true)] [string]$Path + ) + + $resolved = Resolve-Path -Path $Path -ErrorAction SilentlyContinue + if ($null -eq $resolved) { + return $Path + } + + return $resolved.ProviderPath +} + +function Test-ExistsAny { + param( + [Parameter(Mandatory = $true)] [string]$Root, + [Parameter(Mandatory = $true)] [string[]]$Candidates, + [Parameter()] [switch]$Recurse + ) + + foreach ($candidate in $Candidates) { + $path = Join-Path -Path $Root -ChildPath $candidate + if (Test-Path -LiteralPath $path) { + return $path + } + } + + if (-not $Recurse) { + return $null + } + + $allFiles = Get-ChildItem -Path $Root -Recurse -File -ErrorAction SilentlyContinue + foreach ($candidate in $Candidates) { + $found = $allFiles | Where-Object { $_.Name -like $candidate -or $_.FullName -like "*$candidate" } | Select-Object -First 1 + if ($found) { + return $found.FullName + } + } + + return $null +} + +function Test-MissingForbiddenConfigs { + param( + [Parameter(Mandatory = $true)] [string]$Root, + [Parameter(Mandatory = $true)] [string[]]$Patterns + ) + + $files = Get-ChildItem -Path $Root -Recurse -File -ErrorAction SilentlyContinue + $forbidden = @() + + foreach ($pattern in $Patterns) { + $normalized = Join-Path -Path $Root -ChildPath $pattern + $forbidden += $files | Where-Object { $_.FullName -like $normalized } + } + + return $forbidden | Select-Object -ExpandProperty FullName -Unique +} + +function Test-JsonFile { + param( + [Parameter(Mandatory = $true)] [string]$Path + ) + + if (-not (Test-Path -LiteralPath $Path)) { + return $false + } + + $null = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json + return $true +} + +function Assert-Administrator { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [Security.Principal.WindowsPrincipal]::new($identity) + if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + throw 'Booting to recovery requires an elevated PowerShell session.' + } +} + +function Test-ReAgentCInfo { + $reagentc = Join-Path -Path $env:SystemRoot -ChildPath 'System32\reagentc.exe' + if (-not (Test-Path -LiteralPath $reagentc)) { + throw "reagentc.exe was not found: $reagentc" + } + + $output = & $reagentc /info 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "reagentc /info failed with exit code $LASTEXITCODE. $($output -join ' ')" + } + + $text = $output -join "`n" + if ($text -notmatch '(?im)Windows RE status:\s*Enabled') { + throw 'Windows RE is not enabled according to reagentc /info.' + } + + if ($text -notmatch '(?im)Windows RE location:\s*.+') { + throw 'Windows RE location is missing from reagentc /info.' + } + + return $text +} + +$checkResults = [System.Collections.Generic.List[string]]::new() +$errors = [System.Collections.Generic.List[string]]::new() +$root = Resolve-AbsolutePath -Path $WinReRoot + +if (-not (Test-Path -LiteralPath $root)) { + throw "WinRE root path does not exist: $root" +} + +$successCount = 0 + +if ($SkipReAgentC) { + $checkResults.Add('SKIP reagentc /info validation') +} else { + try { + $null = Test-ReAgentCInfo + $checkResults.Add('PASS reagentc /info: Windows RE is enabled') + $successCount++ + } catch { + $errors.Add("FAIL reagentc /info validation: $($_.Exception.Message)") + } +} + +# Launcher artifact (name-based + regex fallback) +$launcher = Test-ExistsAny -Root $root -Candidates $LauncherCandidates +if (-not $launcher) { + $launcherCandidatesRegex = Get-ChildItem -Path $root -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { $_.Name -match '(?i)foundry.*recovery.*\.(exe|bat|cmd|ps1)$' } | + Select-Object -First 1 + + if ($launcherCandidatesRegex) { + $launcher = $launcherCandidatesRegex.FullName + } +} + +if ($launcher) { + $checkResults.Add("PASS Launcher: $launcher") + $successCount++ +} else { + $errors.Add('FAIL Missing WinRE recovery launcher') +} + +# WinREConfig.xml +$winReConfigPath = Test-ExistsAny -Root $root -Candidates @($WinReConfigFile) -Recurse +if ($winReConfigPath) { + $checkResults.Add("PASS WinREConfig: $winReConfigPath") + $successCount++ +} else { + $errors.Add('FAIL Missing WinREConfig.xml') +} + +# Bootstrap script +$bootstrapPath = Test-ExistsAny -Root $root -Candidates @( + $BootstrapScript, + "Windows\\System32\\$BootstrapScript", + "Windows\\System32\\WinPe\\$BootstrapScript" +) -Recurse +if ($bootstrapPath) { + $checkResults.Add("PASS Bootstrap: $bootstrapPath") + $successCount++ +} else { + $errors.Add('FAIL Missing FoundryBootstrap') +} + +# Foundry.Connect executable +$connectMatches = Get-ChildItem -Path $root -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -match 'Foundry\\Runtime\\Foundry\.Connect\\[^\\]+\\Foundry\.Connect\.exe$' } +if ($connectMatches) { + $checkResults.Add("PASS Foundry.Connect: $($connectMatches[0].FullName)") + $successCount++ +} else { + $errors.Add('FAIL Missing Foundry.Connect executable') +} + +# 7-Zip runtime +$sevenZipMatches = Get-ChildItem -Path $root -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -match 'Foundry\\Tools\\7zip\\[^\\]+\\7za\.exe$' } +$sevenZipLicense = Join-Path -Path $root -ChildPath 'Foundry\Tools\7zip\License.txt' +$sevenZipReadme = Join-Path -Path $root -ChildPath 'Foundry\Tools\7zip\readme.txt' +if ($sevenZipMatches -and (Test-Path -LiteralPath $sevenZipLicense) -and (Test-Path -LiteralPath $sevenZipReadme)) { + $checkResults.Add("PASS 7-Zip runtime: $($sevenZipMatches[0].FullName)") + $successCount++ +} else { + $errors.Add('FAIL Missing bundled 7-Zip runtime or license files') +} + +# Minimal recovery config files +$connectConfigPath = Join-Path -Path $root -ChildPath 'Foundry\Config\foundry.connect.config.json' +$deployConfigPath = Join-Path -Path $root -ChildPath 'Foundry\Config\foundry.deploy.config.json' +$timeZoneMapPath = Join-Path -Path $root -ChildPath 'Foundry\Config\iana-windows-timezones.json' + +try { + if (Test-JsonFile -Path $connectConfigPath) { + $checkResults.Add("PASS Foundry.Connect config: $connectConfigPath") + $successCount++ + } else { + $errors.Add('FAIL Missing or invalid Foundry.Connect recovery config') + } + + if (Test-JsonFile -Path $deployConfigPath) { + $checkResults.Add("PASS Foundry.Deploy config: $deployConfigPath") + $successCount++ + } else { + $errors.Add('FAIL Missing or invalid sanitized Foundry.Deploy recovery config') + } + + if (Test-JsonFile -Path $timeZoneMapPath) { + $checkResults.Add("PASS IANA time zone map: $timeZoneMapPath") + $successCount++ + } else { + $errors.Add('FAIL Missing or invalid IANA time zone map') + } +} catch { + $errors.Add("FAIL Invalid recovery JSON payload: $($_.Exception.Message)") +} + +# Ensure Foundry.Deploy is absent +$deployArtifacts = Get-ChildItem -Path (Join-Path $root 'Foundry') -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -match 'Foundry\\Runtime\\Foundry\.Deploy\\' -or $_.FullName -match 'Foundry\\Deploy\\' -or $_.Name -match '(?i)foundry\.deploy\.exe$' } +if ($deployArtifacts.Count -eq 0) { + $checkResults.Add('PASS Foundry.Deploy absent') + $successCount++ +} else { + $errors.Add(("FAIL Foundry.Deploy present: {0}" -f ($deployArtifacts | Select-Object -First 3 | ForEach-Object { $_.FullName } | Join-String -Separator '; '))) +} + +# Excluded configs must be absent +$forbidden = Test-MissingForbiddenConfigs -Root $root -Patterns $ExcludedConfigPatterns +if ($forbidden.Count -eq 0) { + $checkResults.Add('PASS No excluded recovery-time config files') + $successCount++ +} else { + $errors.Add(("FAIL Forbidden config file(s) found: {0}" -f (($forbidden[0..([Math]::Min($forbidden.Count - 1, 4))]) -join '; '))) +} + +Write-Host 'Foundry OS Recovery WinRE validation' -ForegroundColor Cyan +Write-Host "Root: $root" -ForegroundColor DarkGray +Write-Host '' +foreach ($r in $checkResults) { + Write-Host $r -ForegroundColor Green +} + +if ($errors.Count -gt 0) { + Write-Host '' + Write-Host 'Validation errors:' -ForegroundColor Red + foreach ($e in $errors) { + Write-Host "- $e" -ForegroundColor Red + } + Write-Host "`nResult: FAILED (errors: $($errors.Count), passed: $successCount)" -ForegroundColor Red + + if ($BootToRecovery) { + throw 'Validation failed; boot to recovery skipped by design.' + } + + exit 1 +} + +Write-Host "`nResult: PASSED (checks: $successCount)" -ForegroundColor Green + +if (-not $BootToRecovery) { + Write-Host 'Non-destructive mode complete. Use -BootToRecovery -Force to boot into WinRE.' + return +} + +if (-not $Force) { + throw 'Boot to recovery is explicit and destructive; pass -Force to continue.' +} + +Assert-Administrator + +if ($PSCmdlet.ShouldProcess('local machine', 'boot into Windows Recovery (OS Recovery)')) { + Write-Host 'Booting into Windows Recovery Environment...' + shutdown.exe /r /o /t 0 +} diff --git a/src/Foundry.Core.Tests/Configuration/DeployConfigurationGeneratorTests.cs b/src/Foundry.Core.Tests/Configuration/DeployConfigurationGeneratorTests.cs index dedb7ef3..50f45ce1 100644 --- a/src/Foundry.Core.Tests/Configuration/DeployConfigurationGeneratorTests.cs +++ b/src/Foundry.Core.Tests/Configuration/DeployConfigurationGeneratorTests.cs @@ -64,6 +64,32 @@ public void Generate_WhenNetworkProfileRoamingIsEnabled_PropagatesDeployRoaming( Assert.True(result.Network.ProfileRoaming.IncludePrivateKeyMaterial); } + [Fact] + public void Generate_WhenOsRecoveryIsEnabled_PropagatesDeployOsRecoveryFlag() + { + var generator = new DeployConfigurationGenerator(); + + FoundryDeployConfigurationDocument result = generator.Generate(new FoundryConfigurationDocument + { + OsRecovery = new OsRecoverySettings + { + IsEnabled = true + } + }); + + Assert.True(result.OsRecovery.IsEnabled); + } + + [Fact] + public void Generate_WhenOsRecoveryIsNotConfigured_DefaultsDeployOsRecoveryToDisabled() + { + var generator = new DeployConfigurationGenerator(); + + FoundryDeployConfigurationDocument result = generator.Generate(new FoundryConfigurationDocument()); + + Assert.False(result.OsRecovery.IsEnabled); + } + [Fact] public void Generate_PropagatesTelemetrySettings() { diff --git a/src/Foundry.Core.Tests/Configuration/FoundryConfigurationServiceTests.cs b/src/Foundry.Core.Tests/Configuration/FoundryConfigurationServiceTests.cs index 0d445a63..8420f040 100644 --- a/src/Foundry.Core.Tests/Configuration/FoundryConfigurationServiceTests.cs +++ b/src/Foundry.Core.Tests/Configuration/FoundryConfigurationServiceTests.cs @@ -22,6 +22,10 @@ public void Serialize_ThenDeserialize_RoundTripsBusinessSettings() var document = new FoundryConfigurationDocument { + OsRecovery = new OsRecoverySettings + { + IsEnabled = true + }, Network = new NetworkSettings { WifiProvisioned = true, @@ -97,6 +101,7 @@ public void Serialize_ThenDeserialize_RoundTripsBusinessSettings() string json = service.Serialize(document); FoundryConfigurationDocument loaded = service.Deserialize(json); + Assert.True(loaded.OsRecovery.IsEnabled); Assert.True(loaded.Network.WifiProvisioned); Assert.Equal("CorpWiFi", loaded.Network.Wifi.Ssid); Assert.True(loaded.OperatingSystemSelection.IsEnabled); diff --git a/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs b/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs new file mode 100644 index 00000000..1537eba2 --- /dev/null +++ b/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs @@ -0,0 +1,355 @@ +using System.IO.Compression; +using System.Text; +using System.Xml.Linq; +using Foundry.Core.Services.Configuration; +using Foundry.Core.Services.WinPe; +using Foundry.Core.Services.WinPe.OsRecovery; + +namespace Foundry.Core.Tests.WinPe; + +public sealed class OsRecoveryPayloadProvisioningServiceTests +{ + [Fact] + public async Task ProvisionAsync_StagesRequiredFilesAndExcludesWinPeOnlyArtifacts() + { + using TempOsRecoveryWorkspace workspace = TempOsRecoveryWorkspace.Create(); + string connectArchivePath = workspace.CreateArchive("connect.zip", "Foundry.Connect.exe", "connect"); + string bootstrapScript = "Write-Host 'Bootstrap'"; + + var service = new OsRecoveryPayloadProvisioningService( + new EmbeddedLanguageRegistryService(), + new WinPeRuntimePayloadProvisioningService(new FakeRuntimeProcessRunner())); + + WinPeResult result = await service.ProvisionAsync( + new OsRecoveryPayloadProvisioningOptions + { + MountedImagePath = workspace.MountedImagePath, + WorkingDirectoryPath = workspace.WorkingDirectoryPath, + Architecture = WinPeArchitecture.X64, + BootstrapScriptContent = bootstrapScript, + FoundryConnectConfigurationJson = "{\"schemaVersion\":1}", + DeployConfigurationJson = "{\"schemaVersion\":2}", + IanaWindowsTimeZoneMapJson = "{\"zones\":[]}", + SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, + Connect = new WinPeRuntimePayloadApplicationOptions + { + IsEnabled = true, + ArchivePath = connectArchivePath + }, + BootMenuLocalizations = CreateBootMenuLocalizations() + }, + cancellationToken: CancellationToken.None); + + Assert.True(result.IsSuccess, result.Error?.Details); + Assert.NotNull(result.Value); + + string toolsPath = Path.Combine(workspace.MountedImagePath, "Sources", "Recovery", "Tools"); + string system32Path = Path.Combine(workspace.MountedImagePath, "Windows", "System32"); + string configPath = Path.Combine(workspace.MountedImagePath, "Foundry", "Config"); + string connectRuntimePath = Path.Combine(workspace.MountedImagePath, "Foundry", "Runtime", "Foundry.Connect", "win-x64"); + string sevenZipToolsPath = Path.Combine(workspace.MountedImagePath, "Foundry", "Tools", "7zip"); + + Assert.True(File.Exists(Path.Combine(toolsPath, "FoundryRecoveryLauncher.cmd"))); + Assert.True(File.Exists(Path.Combine(toolsPath, "WinREConfig.xml"))); + Assert.Equal(bootstrapScript, await File.ReadAllTextAsync(Path.Combine(system32Path, "FoundryBootstrap.ps1"))); + Assert.Equal("{\"schemaVersion\":1}", await File.ReadAllTextAsync(Path.Combine(configPath, "foundry.connect.config.json"))); + Assert.Equal("{\"schemaVersion\":2}", await File.ReadAllTextAsync(Path.Combine(configPath, "foundry.deploy.config.json"))); + Assert.Equal("{\"zones\":[]}", await File.ReadAllTextAsync(Path.Combine(configPath, "iana-windows-timezones.json"))); + Assert.Equal("connect", await File.ReadAllTextAsync(Path.Combine(connectRuntimePath, "Foundry.Connect.exe"))); + Assert.Equal("7za", await File.ReadAllTextAsync(Path.Combine(sevenZipToolsPath, "x64", "7za.exe"))); + Assert.Equal("license", await File.ReadAllTextAsync(Path.Combine(sevenZipToolsPath, "License.txt"))); + Assert.Equal("readme", await File.ReadAllTextAsync(Path.Combine(sevenZipToolsPath, "readme.txt"))); + + Assert.False(Directory.Exists(Path.Combine(workspace.MountedImagePath, "Foundry", "Runtime", "Foundry.Deploy"))); + Assert.False(File.Exists(Path.Combine(configPath, "foundry.connect.provisioning-source.txt"))); + Assert.False(File.Exists(Path.Combine(configPath, "foundry.deploy.provisioning-source.txt"))); + Assert.False(Directory.Exists(Path.Combine(configPath, "Secrets"))); + Assert.False(Directory.Exists(Path.Combine(configPath, "Network"))); + Assert.False(Directory.Exists(Path.Combine(configPath, "Autopilot"))); + Assert.False(Directory.Exists(Path.Combine(workspace.MountedImagePath, "Foundry", "Runtime", "AutopilotHash"))); + Assert.False(Directory.Exists(Path.Combine(workspace.MountedImagePath, "Foundry", "Tools", "OA3"))); + } + + [Fact] + public async Task ProvisionAsync_WritesWinReConfigXmlAsUtf8WithoutBom() + { + using TempOsRecoveryWorkspace workspace = TempOsRecoveryWorkspace.Create(); + string connectArchivePath = workspace.CreateArchive("connect.zip", "Foundry.Connect.exe", "connect"); + var service = new OsRecoveryPayloadProvisioningService( + new EmbeddedLanguageRegistryService(), + new WinPeRuntimePayloadProvisioningService(new FakeRuntimeProcessRunner())); + + WinPeResult result = await service.ProvisionAsync( + new OsRecoveryPayloadProvisioningOptions + { + MountedImagePath = workspace.MountedImagePath, + WorkingDirectoryPath = workspace.WorkingDirectoryPath, + Architecture = WinPeArchitecture.X64, + BootstrapScriptContent = "bootstrap", + FoundryConnectConfigurationJson = "{}", + DeployConfigurationJson = "{}", + IanaWindowsTimeZoneMapJson = "{}", + SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, + Connect = new WinPeRuntimePayloadApplicationOptions + { + IsEnabled = true, + ArchivePath = connectArchivePath + }, + BootMenuLocalizations = CreateBootMenuLocalizations() + }, + cancellationToken: CancellationToken.None); + + Assert.True(result.IsSuccess, result.Error?.Details); + + string winReConfigPath = Path.Combine(workspace.MountedImagePath, "Sources", "Recovery", "Tools", "WinREConfig.xml"); + byte[] bytes = await File.ReadAllBytesAsync(winReConfigPath); + Assert.False(bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF); + + XDocument document = XDocument.Parse(Encoding.UTF8.GetString(bytes)); + Assert.Equal("Recovery", document.Root?.Name.LocalName); + Assert.Equal( + "FoundryRecoveryLauncher.cmd", + document.Root?.Element("RecoveryTools")?.Element("RelativeFilePath")?.Value); + } + + [Fact] + public async Task ProvisionAsync_ReturnsBootMenuConfigurationForEverySupportedCulture() + { + using TempOsRecoveryWorkspace workspace = TempOsRecoveryWorkspace.Create(); + string connectArchivePath = workspace.CreateArchive("connect.zip", "Foundry.Connect.exe", "connect"); + IReadOnlyList localizations = CreateBootMenuLocalizations(); + var service = new OsRecoveryPayloadProvisioningService( + new EmbeddedLanguageRegistryService(), + new WinPeRuntimePayloadProvisioningService(new FakeRuntimeProcessRunner())); + + WinPeResult result = await service.ProvisionAsync( + new OsRecoveryPayloadProvisioningOptions + { + MountedImagePath = workspace.MountedImagePath, + WorkingDirectoryPath = workspace.WorkingDirectoryPath, + Architecture = WinPeArchitecture.X64, + BootstrapScriptContent = "bootstrap", + FoundryConnectConfigurationJson = "{}", + DeployConfigurationJson = "{}", + IanaWindowsTimeZoneMapJson = "{}", + SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, + Connect = new WinPeRuntimePayloadApplicationOptions + { + IsEnabled = true, + ArchivePath = connectArchivePath + }, + BootMenuLocalizations = localizations + }, + cancellationToken: CancellationToken.None); + + Assert.True(result.IsSuccess, result.Error?.Details); + + XDocument document = XDocument.Parse(result.Value!.BootMenuConfigurationXml); + XElement[] entries = document.Root!.Elements("WinRETool").ToArray(); + Assert.Equal(localizations.Count, entries.Length); + Assert.All(entries, entry => + { + Assert.False(string.IsNullOrWhiteSpace(entry.Attribute("locale")?.Value)); + Assert.False(string.IsNullOrWhiteSpace(entry.Element("Name")?.Value)); + Assert.False(string.IsNullOrWhiteSpace(entry.Element("Description")?.Value)); + }); + } + + [Fact] + public async Task ProvisionAsync_WhenBootMenuLocalizationIsMissingSupportedCulture_ReturnsFailure() + { + using TempOsRecoveryWorkspace workspace = TempOsRecoveryWorkspace.Create(); + string connectArchivePath = workspace.CreateArchive("connect.zip", "Foundry.Connect.exe", "connect"); + List localizations = [.. CreateBootMenuLocalizations()]; + localizations.RemoveAt(localizations.Count - 1); + var service = new OsRecoveryPayloadProvisioningService( + new EmbeddedLanguageRegistryService(), + new WinPeRuntimePayloadProvisioningService(new FakeRuntimeProcessRunner())); + + WinPeResult result = await service.ProvisionAsync( + new OsRecoveryPayloadProvisioningOptions + { + MountedImagePath = workspace.MountedImagePath, + WorkingDirectoryPath = workspace.WorkingDirectoryPath, + Architecture = WinPeArchitecture.X64, + BootstrapScriptContent = "bootstrap", + FoundryConnectConfigurationJson = "{}", + DeployConfigurationJson = "{}", + IanaWindowsTimeZoneMapJson = "{}", + SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, + Connect = new WinPeRuntimePayloadApplicationOptions + { + IsEnabled = true, + ArchivePath = connectArchivePath + }, + BootMenuLocalizations = localizations + }, + cancellationToken: CancellationToken.None); + + Assert.False(result.IsSuccess); + Assert.Contains("supported culture", result.Error?.Details, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task ProvisionAsync_WhenBootMenuTextExceedsThirtyCharacters_ReturnsFailure() + { + using TempOsRecoveryWorkspace workspace = TempOsRecoveryWorkspace.Create(); + string connectArchivePath = workspace.CreateArchive("connect.zip", "Foundry.Connect.exe", "connect"); + List localizations = [.. CreateBootMenuLocalizations()]; + localizations[0] = localizations[0] with + { + Name = "Foundry OS Recovery Utility Name", + Description = "Foundry OS Recovery Utility Description" + }; + + var service = new OsRecoveryPayloadProvisioningService( + new EmbeddedLanguageRegistryService(), + new WinPeRuntimePayloadProvisioningService(new FakeRuntimeProcessRunner())); + + WinPeResult result = await service.ProvisionAsync( + new OsRecoveryPayloadProvisioningOptions + { + MountedImagePath = workspace.MountedImagePath, + WorkingDirectoryPath = workspace.WorkingDirectoryPath, + Architecture = WinPeArchitecture.X64, + BootstrapScriptContent = "bootstrap", + FoundryConnectConfigurationJson = "{}", + DeployConfigurationJson = "{}", + IanaWindowsTimeZoneMapJson = "{}", + SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, + Connect = new WinPeRuntimePayloadApplicationOptions + { + IsEnabled = true, + ArchivePath = connectArchivePath + }, + BootMenuLocalizations = localizations + }, + cancellationToken: CancellationToken.None); + + Assert.False(result.IsSuccess); + Assert.Contains("30 characters", result.Error?.Details, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task ProvisionAsync_WhenManagedPayloadExceedsBudget_ReturnsFailure() + { + using TempOsRecoveryWorkspace workspace = TempOsRecoveryWorkspace.Create(); + string connectArchivePath = workspace.CreateArchive("connect.zip", "Foundry.Connect.exe", new string('c', 256)); + var service = new OsRecoveryPayloadProvisioningService( + new EmbeddedLanguageRegistryService(), + new WinPeRuntimePayloadProvisioningService(new FakeRuntimeProcessRunner())); + + WinPeResult result = await service.ProvisionAsync( + new OsRecoveryPayloadProvisioningOptions + { + MountedImagePath = workspace.MountedImagePath, + WorkingDirectoryPath = workspace.WorkingDirectoryPath, + Architecture = WinPeArchitecture.X64, + BootstrapScriptContent = "bootstrap", + FoundryConnectConfigurationJson = "{}", + DeployConfigurationJson = "{}", + IanaWindowsTimeZoneMapJson = "{}", + SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, + MaxManagedPayloadSizeBytes = 32, + Connect = new WinPeRuntimePayloadApplicationOptions + { + IsEnabled = true, + ArchivePath = connectArchivePath + }, + BootMenuLocalizations = CreateBootMenuLocalizations() + }, + cancellationToken: CancellationToken.None); + + Assert.False(result.IsSuccess); + Assert.Contains("256 MiB", result.Error?.Message, StringComparison.OrdinalIgnoreCase); + Assert.Contains("32", result.Error?.Details, StringComparison.OrdinalIgnoreCase); + } + + private static IReadOnlyList CreateBootMenuLocalizations() + { + return new EmbeddedLanguageRegistryService() + .GetLanguages() + .Select(language => new OsRecoveryBootMenuLocalization + { + Culture = language.Code, + Name = "Foundry Recovery", + Description = "Recover this device" + }) + .ToArray(); + } + + private sealed class FakeRuntimeProcessRunner : IWinPeProcessRunner + { + public Task RunAsync( + string fileName, + string arguments, + string workingDirectory, + CancellationToken cancellationToken, + IReadOnlyDictionary? environmentOverrides = null) + { + throw new NotSupportedException(); + } + + public Task RunCmdScriptAsync( + string scriptPath, + string scriptArguments, + string workingDirectory, + CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } + + public Task RunCmdScriptDirectAsync( + string scriptPath, + string scriptArguments, + string workingDirectory, + CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } + } + + private sealed class TempOsRecoveryWorkspace : IDisposable + { + private TempOsRecoveryWorkspace(string rootPath) + { + RootPath = rootPath; + MountedImagePath = Path.Combine(rootPath, "mount"); + WorkingDirectoryPath = Path.Combine(rootPath, "work"); + SevenZipSourcePath = Path.Combine(rootPath, "7z"); + + Directory.CreateDirectory(MountedImagePath); + Directory.CreateDirectory(WorkingDirectoryPath); + Directory.CreateDirectory(Path.Combine(SevenZipSourcePath, "x64")); + File.WriteAllText(Path.Combine(SevenZipSourcePath, "x64", "7za.exe"), "7za"); + File.WriteAllText(Path.Combine(SevenZipSourcePath, "License.txt"), "license"); + File.WriteAllText(Path.Combine(SevenZipSourcePath, "readme.txt"), "readme"); + } + + public string RootPath { get; } + public string MountedImagePath { get; } + public string WorkingDirectoryPath { get; } + public string SevenZipSourcePath { get; } + + public static TempOsRecoveryWorkspace Create() + { + return new TempOsRecoveryWorkspace(Path.Combine(Path.GetTempPath(), $"foundry-osrecovery-{Guid.NewGuid():N}")); + } + + public string CreateArchive(string archiveName, string executableName, string content) + { + string payloadPath = Path.Combine(RootPath, "payloads", Path.GetFileNameWithoutExtension(archiveName)); + Directory.CreateDirectory(payloadPath); + File.WriteAllText(Path.Combine(payloadPath, executableName), content); + + string archivePath = Path.Combine(RootPath, archiveName); + ZipFile.CreateFromDirectory(payloadPath, archivePath); + return archivePath; + } + + public void Dispose() + { + Directory.Delete(RootPath, recursive: true); + } + } +} diff --git a/src/Foundry.Core/Assets/WinPe/FoundryBootstrap.ps1 b/src/Foundry.Core/Assets/WinPe/FoundryBootstrap.ps1 index ba03da75..40a61a1b 100644 --- a/src/Foundry.Core/Assets/WinPe/FoundryBootstrap.ps1 +++ b/src/Foundry.Core/Assets/WinPe/FoundryBootstrap.ps1 @@ -1844,16 +1844,39 @@ try { Write-ConsoleSection -Title 'Runtime' - # USB cache media takes precedence; otherwise the ISO-backed runtime directory is used. - $usbRuntimeRoot = Get-UsbCacheRuntimeRoot - if (-not [string]::IsNullOrWhiteSpace($usbRuntimeRoot)) { - $bootstrapRoot = $usbRuntimeRoot - $deploymentMode = 'Usb' + $requestedDeploymentMode = [string]$env:FOUNDRY_DEPLOYMENT_MODE + $requestedDeploymentMode = $requestedDeploymentMode.Trim() + + if ($requestedDeploymentMode -ieq 'Recovery') { + $bootstrapRoot = Join-Path $WinPeRoot 'Runtime' + $deploymentMode = 'Recovery' } - else { + elseif ($requestedDeploymentMode -ieq 'Iso') { $bootstrapRoot = Join-Path $WinPeRoot 'Runtime' $deploymentMode = 'Iso' } + elseif ($requestedDeploymentMode -ieq 'Usb') { + $usbRuntimeRoot = Get-UsbCacheRuntimeRoot + $bootstrapRoot = if ([string]::IsNullOrWhiteSpace($usbRuntimeRoot)) { + Join-Path $WinPeRoot 'Runtime' + } + else { + $usbRuntimeRoot + } + $deploymentMode = 'Usb' + } + else { + # USB cache media takes precedence; otherwise the ISO-backed runtime directory is used. + $usbRuntimeRoot = Get-UsbCacheRuntimeRoot + if (-not [string]::IsNullOrWhiteSpace($usbRuntimeRoot)) { + $bootstrapRoot = $usbRuntimeRoot + $deploymentMode = 'Usb' + } + else { + $bootstrapRoot = Join-Path $WinPeRoot 'Runtime' + $deploymentMode = 'Iso' + } + } Ensure-Directory -Path $bootstrapRoot @@ -1945,8 +1968,8 @@ try { } elseif ($deploymentMode -ne 'Usb') { Write-Log ` - 'Skipping Foundry.Connect cache verification because the deployment mode is ISO.' ` - -ConsoleMessage 'Foundry.Connect: update check skipped in ISO mode.' + "Skipping Foundry.Connect cache verification because the deployment mode is $deploymentMode." ` + -ConsoleMessage "Foundry.Connect: update check skipped in $deploymentMode mode." } else { try { diff --git a/src/Foundry.Core/Assets/WinRe/FoundryRecoveryLauncher.cmd b/src/Foundry.Core/Assets/WinRe/FoundryRecoveryLauncher.cmd new file mode 100644 index 00000000..60f9ba05 --- /dev/null +++ b/src/Foundry.Core/Assets/WinRe/FoundryRecoveryLauncher.cmd @@ -0,0 +1,14 @@ +@echo off +setlocal + +set "FOUNDRY_DEPLOYMENT_MODE=Recovery" +set "BOOTSTRAP_PATH=%SystemRoot%\System32\FoundryBootstrap.ps1" + +if not exist "%BOOTSTRAP_PATH%" ( + echo Missing Foundry bootstrap: %BOOTSTRAP_PATH% + exit /b 1 +) + +powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%BOOTSTRAP_PATH%" + +exit /b %ERRORLEVEL% diff --git a/src/Foundry.Core/Foundry.Core.csproj b/src/Foundry.Core/Foundry.Core.csproj index e5f1a17a..6184da78 100644 --- a/src/Foundry.Core/Foundry.Core.csproj +++ b/src/Foundry.Core/Foundry.Core.csproj @@ -20,6 +20,9 @@ Foundry.Core.WinPe.ProvisionUsbDisk + + Foundry.Core.WinRe.FoundryRecoveryLauncher + diff --git a/src/Foundry.Core/Models/Configuration/ConfigurationSchemaVersions.cs b/src/Foundry.Core/Models/Configuration/ConfigurationSchemaVersions.cs index 88b34bc0..bbee6d01 100644 --- a/src/Foundry.Core/Models/Configuration/ConfigurationSchemaVersions.cs +++ b/src/Foundry.Core/Models/Configuration/ConfigurationSchemaVersions.cs @@ -2,11 +2,11 @@ namespace Foundry.Core.Models.Configuration; public static class ConfigurationSchemaVersions { - public const int FoundryCurrent = 10; + public const int FoundryCurrent = 11; public const int ConnectCurrent = 2; - public const int DeployCurrent = 8; + public const int DeployCurrent = 9; public static bool IsBootMediaUpdateRecommended(int schemaVersion, int currentSchemaVersion) { diff --git a/src/Foundry.Core/Models/Configuration/Deploy/DeployOsRecoverySettings.cs b/src/Foundry.Core/Models/Configuration/Deploy/DeployOsRecoverySettings.cs new file mode 100644 index 00000000..1b7cfbca --- /dev/null +++ b/src/Foundry.Core/Models/Configuration/Deploy/DeployOsRecoverySettings.cs @@ -0,0 +1,12 @@ +namespace Foundry.Core.Models.Configuration.Deploy; + +/// +/// Describes whether Foundry.Deploy should expect Windows RE OS recovery integration. +/// +public sealed record DeployOsRecoverySettings +{ + /// + /// Gets a value indicating whether the OS recovery integration is enabled. + /// + public bool IsEnabled { get; init; } +} diff --git a/src/Foundry.Core/Models/Configuration/Deploy/FoundryDeployConfigurationDocument.cs b/src/Foundry.Core/Models/Configuration/Deploy/FoundryDeployConfigurationDocument.cs index c0589be7..1087ab7d 100644 --- a/src/Foundry.Core/Models/Configuration/Deploy/FoundryDeployConfigurationDocument.cs +++ b/src/Foundry.Core/Models/Configuration/Deploy/FoundryDeployConfigurationDocument.cs @@ -28,6 +28,11 @@ public sealed record FoundryDeployConfigurationDocument /// public DeployLocalizationSettings Localization { get; init; } = new(); + /// + /// Gets Windows RE OS recovery settings used during deployment. + /// + public DeployOsRecoverySettings OsRecovery { get; init; } = new(); + /// /// Gets network profile roaming settings used during deployment. /// diff --git a/src/Foundry.Core/Models/Configuration/FoundryConfigurationDocument.cs b/src/Foundry.Core/Models/Configuration/FoundryConfigurationDocument.cs index 96e88b59..4b7329e9 100644 --- a/src/Foundry.Core/Models/Configuration/FoundryConfigurationDocument.cs +++ b/src/Foundry.Core/Models/Configuration/FoundryConfigurationDocument.cs @@ -37,6 +37,11 @@ public sealed record FoundryConfigurationDocument /// public LocalizationSettings Localization { get; init; } = new(); + /// + /// Gets user-authored Windows RE OS recovery settings. + /// + public OsRecoverySettings OsRecovery { get; init; } = new(); + /// /// Gets user-authored customization settings used when deployment configuration is generated. /// diff --git a/src/Foundry.Core/Models/Configuration/OsRecoverySettings.cs b/src/Foundry.Core/Models/Configuration/OsRecoverySettings.cs new file mode 100644 index 00000000..d226cc18 --- /dev/null +++ b/src/Foundry.Core/Models/Configuration/OsRecoverySettings.cs @@ -0,0 +1,12 @@ +namespace Foundry.Core.Models.Configuration; + +/// +/// Describes whether Foundry should prepare the Windows Recovery Environment custom tool payload. +/// +public sealed record OsRecoverySettings +{ + /// + /// Gets a value indicating whether the OS recovery payload should be generated. + /// + public bool IsEnabled { get; init; } +} diff --git a/src/Foundry.Core/Services/Configuration/DeployConfigurationGenerator.cs b/src/Foundry.Core/Services/Configuration/DeployConfigurationGenerator.cs index 0edb6a40..90ccabbd 100644 --- a/src/Foundry.Core/Services/Configuration/DeployConfigurationGenerator.cs +++ b/src/Foundry.Core/Services/Configuration/DeployConfigurationGenerator.cs @@ -35,6 +35,10 @@ public FoundryDeployConfigurationDocument Generate(FoundryConfigurationDocument { DefaultTimeZoneId = document.Localization.DefaultTimeZoneId }, + OsRecovery = new DeployOsRecoverySettings + { + IsEnabled = document.OsRecovery.IsEnabled + }, Network = new DeployNetworkSettings { ProfileRoaming = new DeployNetworkProfileRoamingSettings diff --git a/src/Foundry.Core/Services/WinPe/OsRecovery/IOsRecoveryPayloadProvisioningService.cs b/src/Foundry.Core/Services/WinPe/OsRecovery/IOsRecoveryPayloadProvisioningService.cs new file mode 100644 index 00000000..9b097671 --- /dev/null +++ b/src/Foundry.Core/Services/WinPe/OsRecovery/IOsRecoveryPayloadProvisioningService.cs @@ -0,0 +1,9 @@ +namespace Foundry.Core.Services.WinPe.OsRecovery; + +public interface IOsRecoveryPayloadProvisioningService +{ + Task> ProvisionAsync( + OsRecoveryPayloadProvisioningOptions options, + IProgress? downloadProgress = null, + CancellationToken cancellationToken = default); +} diff --git a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryBootMenuLocalization.cs b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryBootMenuLocalization.cs new file mode 100644 index 00000000..eb88ad8a --- /dev/null +++ b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryBootMenuLocalization.cs @@ -0,0 +1,8 @@ +namespace Foundry.Core.Services.WinPe.OsRecovery; + +public sealed record OsRecoveryBootMenuLocalization +{ + public string Culture { get; init; } = string.Empty; + public string Name { get; init; } = string.Empty; + public string Description { get; init; } = string.Empty; +} diff --git a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs new file mode 100644 index 00000000..0964eaca --- /dev/null +++ b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs @@ -0,0 +1,18 @@ +namespace Foundry.Core.Services.WinPe.OsRecovery; + +public sealed record OsRecoveryPayloadProvisioningOptions +{ + public const long DefaultManagedPayloadSizeBytes = 256L * 1024L * 1024L; + + public string MountedImagePath { get; init; } = string.Empty; + public string WorkingDirectoryPath { get; init; } = string.Empty; + public WinPeArchitecture Architecture { get; init; } = WinPeArchitecture.X64; + public string BootstrapScriptContent { get; init; } = string.Empty; + public string FoundryConnectConfigurationJson { get; init; } = string.Empty; + public string DeployConfigurationJson { get; init; } = string.Empty; + public string IanaWindowsTimeZoneMapJson { get; init; } = string.Empty; + public string SevenZipSourceDirectoryPath { get; init; } = string.Empty; + public WinPeRuntimePayloadApplicationOptions Connect { get; init; } = new(); + public IReadOnlyList BootMenuLocalizations { get; init; } = []; + public long MaxManagedPayloadSizeBytes { get; init; } = DefaultManagedPayloadSizeBytes; +} diff --git a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningResult.cs b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningResult.cs new file mode 100644 index 00000000..eef53bc2 --- /dev/null +++ b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningResult.cs @@ -0,0 +1,7 @@ +namespace Foundry.Core.Services.WinPe.OsRecovery; + +public sealed record OsRecoveryPayloadProvisioningResult +{ + public required string BootMenuConfigurationXml { get; init; } + public required long ManagedPayloadSizeBytes { get; init; } +} diff --git a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs new file mode 100644 index 00000000..31861842 --- /dev/null +++ b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs @@ -0,0 +1,385 @@ +using System.Reflection; +using System.Text; +using System.Xml.Linq; +using Foundry.Core.Services.Configuration; + +namespace Foundry.Core.Services.WinPe.OsRecovery; + +public sealed class OsRecoveryPayloadProvisioningService : IOsRecoveryPayloadProvisioningService +{ + private const string LauncherResourceName = "Foundry.Core.WinRe.FoundryRecoveryLauncher"; + private const string LauncherFileName = "FoundryRecoveryLauncher.cmd"; + private const string WinReConfigFileName = "WinREConfig.xml"; + private const string BootstrapFileName = "FoundryBootstrap.ps1"; + private static readonly UTF8Encoding Utf8NoBom = new(false); + + private readonly ILanguageRegistryService _languageRegistryService; + private readonly IWinPeRuntimePayloadProvisioningService _runtimePayloadProvisioningService; + + public OsRecoveryPayloadProvisioningService() + : this( + new EmbeddedLanguageRegistryService(), + new WinPeRuntimePayloadProvisioningService()) + { + } + + internal OsRecoveryPayloadProvisioningService( + ILanguageRegistryService languageRegistryService, + IWinPeRuntimePayloadProvisioningService runtimePayloadProvisioningService) + { + _languageRegistryService = languageRegistryService; + _runtimePayloadProvisioningService = runtimePayloadProvisioningService; + } + + public async Task> ProvisionAsync( + OsRecoveryPayloadProvisioningOptions options, + IProgress? downloadProgress = null, + CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + WinPeDiagnostic? validationError = ValidateOptions(options); + if (validationError is not null) + { + return WinPeResult.Failure(validationError); + } + + try + { + string mountedImagePath = Path.GetFullPath(options.MountedImagePath); + string recoveryToolsPath = Path.Combine(mountedImagePath, "Sources", "Recovery", "Tools"); + string system32Path = Path.Combine(mountedImagePath, "Windows", "System32"); + string foundryConfigPath = Path.Combine(mountedImagePath, "Foundry", "Config"); + + Directory.CreateDirectory(recoveryToolsPath); + Directory.CreateDirectory(system32Path); + Directory.CreateDirectory(foundryConfigPath); + + string launcherContent = LoadLauncherContent(); + string winReConfigXml = CreateWinReConfigurationXml(); + string bootMenuConfigurationXml = CreateBootMenuConfigurationXml(options.BootMenuLocalizations); + + await File.WriteAllTextAsync( + Path.Combine(recoveryToolsPath, LauncherFileName), + launcherContent, + Utf8NoBom, + cancellationToken).ConfigureAwait(false); + await File.WriteAllTextAsync( + Path.Combine(recoveryToolsPath, WinReConfigFileName), + winReConfigXml, + Utf8NoBom, + cancellationToken).ConfigureAwait(false); + await File.WriteAllTextAsync( + Path.Combine(system32Path, BootstrapFileName), + options.BootstrapScriptContent, + Utf8NoBom, + cancellationToken).ConfigureAwait(false); + await File.WriteAllTextAsync( + Path.Combine(foundryConfigPath, "foundry.connect.config.json"), + options.FoundryConnectConfigurationJson, + Utf8NoBom, + cancellationToken).ConfigureAwait(false); + await File.WriteAllTextAsync( + Path.Combine(foundryConfigPath, "foundry.deploy.config.json"), + options.DeployConfigurationJson, + Utf8NoBom, + cancellationToken).ConfigureAwait(false); + await File.WriteAllTextAsync( + Path.Combine(foundryConfigPath, "iana-windows-timezones.json"), + options.IanaWindowsTimeZoneMapJson, + Utf8NoBom, + cancellationToken).ConfigureAwait(false); + + ProvisionBundledSevenZip(mountedImagePath, options); + + WinPeResult runtimeProvisioningResult = await _runtimePayloadProvisioningService.ProvisionAsync( + new WinPeRuntimePayloadProvisioningOptions + { + Architecture = options.Architecture, + WorkingDirectoryPath = options.WorkingDirectoryPath, + MountedImagePath = mountedImagePath, + Connect = options.Connect + }, + downloadProgress, + cancellationToken).ConfigureAwait(false); + + if (!runtimeProvisioningResult.IsSuccess) + { + return WinPeResult.Failure(runtimeProvisioningResult.Error!); + } + + long managedPayloadSizeBytes = CalculateManagedPayloadSizeBytes(mountedImagePath, options.Architecture); + if (managedPayloadSizeBytes > options.MaxManagedPayloadSizeBytes) + { + return WinPeResult.Failure( + WinPeErrorCodes.ValidationFailed, + "Foundry OS recovery managed payload exceeds the default 256 MiB size budget.", + $"Managed payload size is {managedPayloadSizeBytes} bytes. Configured budget is {options.MaxManagedPayloadSizeBytes} bytes."); + } + + return WinPeResult.Success(new OsRecoveryPayloadProvisioningResult + { + BootMenuConfigurationXml = bootMenuConfigurationXml, + ManagedPayloadSizeBytes = managedPayloadSizeBytes + }); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or ArgumentException or NotSupportedException or InvalidOperationException) + { + return WinPeResult.Failure( + WinPeErrorCodes.BuildFailed, + "Failed to provision the Foundry OS recovery payload.", + ex.Message); + } + } + + private string CreateBootMenuConfigurationXml(IReadOnlyList localizations) + { + IReadOnlyList supportedCultures = _languageRegistryService + .GetLanguages() + .Select(language => LanguageCodeUtility.Canonicalize(language.Code)) + .ToArray(); + + Dictionary localizationMap = localizations + .GroupBy(localization => LanguageCodeUtility.Canonicalize(localization.Culture), StringComparer.OrdinalIgnoreCase) + .ToDictionary( + group => group.Key, + group => + { + if (group.Count() > 1) + { + throw new ArgumentException($"Duplicate OS recovery boot menu localization was provided for culture '{group.Key}'."); + } + + return group.Single() with + { + Culture = group.Key + }; + }, + StringComparer.OrdinalIgnoreCase); + + foreach (string culture in supportedCultures) + { + if (!localizationMap.ContainsKey(culture)) + { + throw new ArgumentException($"An OS recovery boot menu localization is required for every supported culture. Missing culture: '{culture}'."); + } + } + + XElement[] entries = supportedCultures + .Select(culture => CreateBootMenuEntry(localizationMap[culture])) + .ToArray(); + + var document = new XDocument( + new XDeclaration("1.0", "utf-8", null), + new XElement("BootShell", entries)); + + return document.ToString(SaveOptions.DisableFormatting); + } + + private static XElement CreateBootMenuEntry(OsRecoveryBootMenuLocalization localization) + { + string name = localization.Name.Trim(); + string description = localization.Description.Trim(); + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException($"OS recovery boot menu name is required for culture '{localization.Culture}'."); + } + + if (string.IsNullOrWhiteSpace(description)) + { + throw new ArgumentException($"OS recovery boot menu description is required for culture '{localization.Culture}'."); + } + + if (name.Length > 30 || description.Length > 30) + { + throw new ArgumentException( + $"OS recovery boot menu name and description must not exceed 30 characters for culture '{localization.Culture}'."); + } + + return new XElement( + "WinRETool", + new XAttribute("locale", LanguageCodeUtility.Canonicalize(localization.Culture).ToLowerInvariant()), + new XElement("Name", name), + new XElement("Description", description)); + } + + private static string CreateWinReConfigurationXml() + { + var document = new XDocument( + new XDeclaration("1.0", "utf-8", null), + new XElement( + "Recovery", + new XElement( + "RecoveryTools", + new XElement("RelativeFilePath", LauncherFileName)))); + + return document.ToString(SaveOptions.DisableFormatting); + } + + private static long CalculateManagedPayloadSizeBytes(string mountedImagePath, WinPeArchitecture architecture) + { + string runtimeIdentifier = architecture.ToDotnetRuntimeIdentifier(); + string sevenZipRuntimeFolder = architecture.ToSevenZipRuntimeFolder(); + string[] managedPaths = + [ + Path.Combine(mountedImagePath, "Sources", "Recovery", "Tools", LauncherFileName), + Path.Combine(mountedImagePath, "Sources", "Recovery", "Tools", WinReConfigFileName), + Path.Combine(mountedImagePath, "Windows", "System32", BootstrapFileName), + Path.Combine(mountedImagePath, "Foundry", "Config", "foundry.connect.config.json"), + Path.Combine(mountedImagePath, "Foundry", "Config", "foundry.deploy.config.json"), + Path.Combine(mountedImagePath, "Foundry", "Config", "iana-windows-timezones.json"), + Path.Combine(mountedImagePath, "Foundry", "Runtime", "Foundry.Connect", runtimeIdentifier), + Path.Combine(mountedImagePath, "Foundry", "Tools", "7zip", sevenZipRuntimeFolder), + Path.Combine(mountedImagePath, "Foundry", "Tools", "7zip", "License.txt"), + Path.Combine(mountedImagePath, "Foundry", "Tools", "7zip", "readme.txt") + ]; + + return managedPaths.Sum(CalculatePathSizeBytes); + } + + private static long CalculatePathSizeBytes(string path) + { + if (File.Exists(path)) + { + return new FileInfo(path).Length; + } + + if (!Directory.Exists(path)) + { + return 0; + } + + return Directory + .EnumerateFiles(path, "*", SearchOption.AllDirectories) + .Sum(filePath => new FileInfo(filePath).Length); + } + + private static void ProvisionBundledSevenZip(string mountedImagePath, OsRecoveryPayloadProvisioningOptions options) + { + string runtimeFolder = options.Architecture.ToSevenZipRuntimeFolder(); + string sourceRootPath = options.SevenZipSourceDirectoryPath; + string sourceExecutablePath = Path.Combine(sourceRootPath, runtimeFolder, "7za.exe"); + string sourceLicensePath = Path.Combine(sourceRootPath, "License.txt"); + string sourceReadmePath = Path.Combine(sourceRootPath, "readme.txt"); + + if (!File.Exists(sourceExecutablePath) || !File.Exists(sourceLicensePath) || !File.Exists(sourceReadmePath)) + { + throw new IOException($"Bundled 7-Zip assets are incomplete under '{sourceRootPath}' for runtime '{runtimeFolder}'."); + } + + string destinationToolsRootPath = Path.Combine(mountedImagePath, "Foundry", "Tools", "7zip"); + string destinationRuntimePath = Path.Combine(destinationToolsRootPath, runtimeFolder); + Directory.CreateDirectory(destinationRuntimePath); + + File.Copy(sourceExecutablePath, Path.Combine(destinationRuntimePath, "7za.exe"), overwrite: true); + File.Copy(sourceLicensePath, Path.Combine(destinationToolsRootPath, "License.txt"), overwrite: true); + File.Copy(sourceReadmePath, Path.Combine(destinationToolsRootPath, "readme.txt"), overwrite: true); + } + + private static string LoadLauncherContent() + { + Assembly assembly = typeof(OsRecoveryPayloadProvisioningService).Assembly; + using Stream? stream = assembly.GetManifestResourceStream(LauncherResourceName); + if (stream is null) + { + throw new InvalidOperationException($"Embedded recovery launcher resource '{LauncherResourceName}' was not found."); + } + + using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true); + return reader.ReadToEnd(); + } + + private static WinPeDiagnostic? ValidateOptions(OsRecoveryPayloadProvisioningOptions? options) + { + if (options is null) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "OS recovery payload provisioning options are required.", + "Provide a non-null OsRecoveryPayloadProvisioningOptions instance."); + } + + if (string.IsNullOrWhiteSpace(options.MountedImagePath)) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "Mounted image path is required for OS recovery payload provisioning.", + "Set OsRecoveryPayloadProvisioningOptions.MountedImagePath."); + } + + if (string.IsNullOrWhiteSpace(options.WorkingDirectoryPath)) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "Working directory path is required for OS recovery payload provisioning.", + "Set OsRecoveryPayloadProvisioningOptions.WorkingDirectoryPath."); + } + + if (!Enum.IsDefined(options.Architecture)) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "WinPE architecture value is invalid.", + $"Value: '{options.Architecture}'."); + } + + if (string.IsNullOrWhiteSpace(options.BootstrapScriptContent)) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "Foundry bootstrap script content is required for OS recovery payload provisioning.", + "Set OsRecoveryPayloadProvisioningOptions.BootstrapScriptContent."); + } + + if (string.IsNullOrWhiteSpace(options.FoundryConnectConfigurationJson)) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "Foundry.Connect configuration JSON is required for OS recovery payload provisioning.", + "Set OsRecoveryPayloadProvisioningOptions.FoundryConnectConfigurationJson."); + } + + if (string.IsNullOrWhiteSpace(options.DeployConfigurationJson)) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "Foundry.Deploy configuration JSON is required for OS recovery payload provisioning.", + "Set OsRecoveryPayloadProvisioningOptions.DeployConfigurationJson."); + } + + if (string.IsNullOrWhiteSpace(options.IanaWindowsTimeZoneMapJson)) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "IANA Windows time zone map JSON is required for OS recovery payload provisioning.", + "Set OsRecoveryPayloadProvisioningOptions.IanaWindowsTimeZoneMapJson."); + } + + if (string.IsNullOrWhiteSpace(options.SevenZipSourceDirectoryPath)) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "Bundled 7-Zip source directory is required for OS recovery payload provisioning.", + "Set OsRecoveryPayloadProvisioningOptions.SevenZipSourceDirectoryPath."); + } + + if (!options.Connect.IsEnabled) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "Foundry.Connect runtime payload provisioning must be enabled for OS recovery.", + "Set OsRecoveryPayloadProvisioningOptions.Connect.IsEnabled to true."); + } + + if (options.MaxManagedPayloadSizeBytes <= 0) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "Managed payload size budget must be greater than zero.", + $"Configured budget: '{options.MaxManagedPayloadSizeBytes}'."); + } + + return null; + } +} diff --git a/src/Foundry.Deploy.Tests/DeploymentLaunchPreparationServiceTests.cs b/src/Foundry.Deploy.Tests/DeploymentLaunchPreparationServiceTests.cs index 5b8f1fab..9ad1f69e 100644 --- a/src/Foundry.Deploy.Tests/DeploymentLaunchPreparationServiceTests.cs +++ b/src/Foundry.Deploy.Tests/DeploymentLaunchPreparationServiceTests.cs @@ -92,6 +92,10 @@ public void Prepare_WhenRequestIsValidAndConfirmed_ReturnsDeploymentContext() RemoveCopilot = true, DisableRecall = true }; + DeployOsRecoverySettings osRecovery = new() + { + IsEnabled = true + }; DeploymentLaunchPreparationResult result = service.Prepare( CreateRequest( @@ -102,6 +106,7 @@ public void Prepare_WhenRequestIsValidAndConfirmed_ReturnsDeploymentContext() defaultTimeZoneId: " Romance Standard Time ", isAutopilotEnabled: true, selectedAutopilotProfile: autopilotProfile, + osRecovery: osRecovery, oobe: oobe, appxRemoval: appxRemoval, aiComponentRemoval: aiComponentRemoval)); @@ -114,6 +119,7 @@ public void Prepare_WhenRequestIsValidAndConfirmed_ReturnsDeploymentContext() Assert.Equal("Romance Standard Time", result.Context?.DefaultTimeZoneId); Assert.Same(driverPack, result.Context?.DriverPack); Assert.Same(autopilotProfile, result.Context?.SelectedAutopilotProfile); + Assert.Same(osRecovery, result.Context?.OsRecovery); Assert.Same(oobe, result.Context?.Oobe); Assert.Same(appxRemoval, result.Context?.AppxRemoval); Assert.Same(aiComponentRemoval, result.Context?.AiComponentRemoval); @@ -235,8 +241,39 @@ public void Prepare_WhenConfirmationIsShown_UsesLocalizedWarningText() } } + [Fact] + public void Prepare_WhenRecoveryModeIsUsed_ShowsRecoverySpecificWarningText() + { + CultureInfo originalCulture = CultureInfo.CurrentCulture; + CultureInfo originalUiCulture = CultureInfo.CurrentUICulture; + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US"); + CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US"); + + try + { + var shell = new FakeApplicationShellService { ConfirmationResult = true }; + var service = new DeploymentLaunchPreparationService(shell); + + DeploymentLaunchPreparationResult result = service.Prepare(CreateRequest( + selectedTargetDisk: CreateDisk(), + mode: DeploymentMode.Recovery)); + + Assert.True(result.IsReadyToStart); + Assert.Equal("Confirm OS Recovery", shell.LastConfirmationTitle); + Assert.Contains("preserve EFI, MSR, and Recovery partitions", shell.LastConfirmationMessage); + Assert.Contains("replace the Windows partition", shell.LastConfirmationMessage); + Assert.Contains("Continue with OS Recovery?", shell.LastConfirmationMessage); + } + finally + { + CultureInfo.CurrentCulture = originalCulture; + CultureInfo.CurrentUICulture = originalUiCulture; + } + } + private static DeploymentLaunchRequest CreateRequest( TargetDiskInfo? selectedTargetDisk, + DeploymentMode mode = DeploymentMode.Usb, string targetComputerName = "LAB-01", string? defaultTimeZoneId = null, DriverPackSelectionKind driverPackSelectionKind = DriverPackSelectionKind.None, @@ -245,6 +282,7 @@ private static DeploymentLaunchRequest CreateRequest( AutopilotProvisioningMode autopilotProvisioningMode = AutopilotProvisioningMode.JsonProfile, AutopilotProfileCatalogItem? selectedAutopilotProfile = null, DeployAutopilotHardwareHashUploadSettings? autopilotHardwareHashUpload = null, + DeployOsRecoverySettings? osRecovery = null, DeployOobeSettings? oobe = null, DeployAppxRemovalSettings? appxRemoval = null, DeployAiComponentRemovalSettings? aiComponentRemoval = null, @@ -252,7 +290,7 @@ private static DeploymentLaunchRequest CreateRequest( { return new DeploymentLaunchRequest { - Mode = DeploymentMode.Usb, + Mode = mode, CacheRootPath = @"X:\Foundry\Runtime", TargetComputerName = targetComputerName, DefaultTimeZoneId = defaultTimeZoneId, @@ -275,6 +313,7 @@ private static DeploymentLaunchRequest CreateRequest( AutopilotProvisioningMode = autopilotProvisioningMode, SelectedAutopilotProfile = selectedAutopilotProfile, AutopilotHardwareHashUpload = autopilotHardwareHashUpload ?? new DeployAutopilotHardwareHashUploadSettings(), + OsRecovery = osRecovery ?? new DeployOsRecoverySettings(), Oobe = oobe ?? new DeployOobeSettings(), AppxRemoval = appxRemoval ?? new DeployAppxRemovalSettings(), AiComponentRemoval = aiComponentRemoval ?? new DeployAiComponentRemovalSettings(), diff --git a/src/Foundry.Deploy.Tests/DeploymentOrchestratorTests.cs b/src/Foundry.Deploy.Tests/DeploymentOrchestratorTests.cs index a1ec7ac0..68b295b8 100644 --- a/src/Foundry.Deploy.Tests/DeploymentOrchestratorTests.cs +++ b/src/Foundry.Deploy.Tests/DeploymentOrchestratorTests.cs @@ -12,13 +12,19 @@ namespace Foundry.Deploy.Tests; public sealed class DeploymentOrchestratorTests { [Fact] - public void DeploymentStepNames_All_OrdersAutopilotProvisioningAfterRecoverySeal() + public void DeploymentStepNames_All_OrdersOsRecoveryAfterDriverPackAndBeforeFirmware() { List steps = DeploymentStepNames.All.ToList(); + int driverPackIndex = steps.IndexOf(DeploymentStepNames.ApplyDriverPack); + int osRecoveryIndex = steps.IndexOf(DeploymentStepNames.ProvisionOsRecovery); + int firmwareIndex = steps.IndexOf(DeploymentStepNames.DownloadFirmwareUpdate); int sealIndex = steps.IndexOf(DeploymentStepNames.SealRecoveryPartition); int autopilotIndex = steps.IndexOf(DeploymentStepNames.ProvisionAutopilot); int finalizeIndex = steps.IndexOf(DeploymentStepNames.FinalizeDeploymentAndWriteLogs); + Assert.True(driverPackIndex >= 0); + Assert.Equal(driverPackIndex + 1, osRecoveryIndex); + Assert.Equal(osRecoveryIndex + 1, firmwareIndex); Assert.True(sealIndex >= 0); Assert.Equal(sealIndex + 1, autopilotIndex); Assert.Equal(autopilotIndex + 1, finalizeIndex); diff --git a/src/Foundry.Deploy.Tests/DeploymentRuntimeContextServiceTests.cs b/src/Foundry.Deploy.Tests/DeploymentRuntimeContextServiceTests.cs new file mode 100644 index 00000000..4c13efe2 --- /dev/null +++ b/src/Foundry.Deploy.Tests/DeploymentRuntimeContextServiceTests.cs @@ -0,0 +1,27 @@ +using Foundry.Deploy.Models; +using Foundry.Deploy.Services.Runtime; + +namespace Foundry.Deploy.Tests; + +public sealed class DeploymentRuntimeContextServiceTests : IDisposable +{ + private const string DeploymentModeEnvironmentVariable = "FOUNDRY_DEPLOYMENT_MODE"; + private readonly string? _originalMode = Environment.GetEnvironmentVariable(DeploymentModeEnvironmentVariable); + + [Fact] + public void Resolve_WhenEnvironmentModeIsRecovery_ReturnsRecoveryContext() + { + Environment.SetEnvironmentVariable(DeploymentModeEnvironmentVariable, "Recovery"); + var service = new DeploymentRuntimeContextService(); + + DeploymentRuntimeContext context = service.Resolve(); + + Assert.Equal(DeploymentMode.Recovery, context.Mode); + Assert.Null(context.UsbCacheRuntimeRoot); + } + + public void Dispose() + { + Environment.SetEnvironmentVariable(DeploymentModeEnvironmentVariable, _originalMode); + } +} diff --git a/src/Foundry.Deploy.Tests/DeploymentStepExecutionContextTests.cs b/src/Foundry.Deploy.Tests/DeploymentStepExecutionContextTests.cs index 2aff1383..4bae9f55 100644 --- a/src/Foundry.Deploy.Tests/DeploymentStepExecutionContextTests.cs +++ b/src/Foundry.Deploy.Tests/DeploymentStepExecutionContextTests.cs @@ -107,6 +107,22 @@ public void ResolveDriverPackCacheRoot_WhenIsoTargetRootIsResolved_UsesTargetCac Assert.Equal(Path.Combine(targetFoundryRoot, "Cache", "DriverPacks"), root); } + [Fact] + public void ResolveOperatingSystemCacheRoot_WhenRecoveryTargetRootIsResolved_UsesTargetCacheLayout() + { + using TempDeploymentWorkspace workspace = TempDeploymentWorkspace.Create(); + string targetFoundryRoot = Path.Combine(workspace.RootPath, "Windows", "Foundry"); + DeploymentStepExecutionContext context = CreateExecutionContext( + workspace.RootPath, + resolvedCacheRootPath: Path.Combine(workspace.CacheRootPath, "Runtime"), + mode: DeploymentMode.Recovery, + targetFoundryRoot: targetFoundryRoot); + + string root = context.ResolveOperatingSystemCacheRoot(); + + Assert.Equal(Path.Combine(targetFoundryRoot, "Cache", "OperatingSystems"), root); + } + private static DeploymentStepExecutionContext CreateExecutionContext( string workspaceRoot, string resolvedCacheRootPath, diff --git a/src/Foundry.Deploy.Tests/PreOobeNetworkProfileRoamingStepTests.cs b/src/Foundry.Deploy.Tests/PreOobeNetworkProfileRoamingStepTests.cs index 62a83612..ae9f86e4 100644 --- a/src/Foundry.Deploy.Tests/PreOobeNetworkProfileRoamingStepTests.cs +++ b/src/Foundry.Deploy.Tests/PreOobeNetworkProfileRoamingStepTests.cs @@ -166,6 +166,19 @@ public DriverPackExecutionPlan Resolve( private sealed class FakeWindowsDeploymentService : IWindowsDeploymentService { public Task PrepareTargetDiskAsync(int diskNumber, string workingDirectory, CancellationToken cancellationToken = default) + { + return PrepareTargetDiskAsync( + diskNumber, + workingDirectory, + RecoveryTargetDiskLayoutMode.FullWipe, + cancellationToken); + } + + public Task PrepareTargetDiskAsync( + int diskNumber, + string workingDirectory, + RecoveryTargetDiskLayoutMode layoutMode, + CancellationToken cancellationToken = default) { throw new NotSupportedException(); } diff --git a/src/Foundry.Deploy.Tests/PrepareTargetDiskLayoutStepTests.cs b/src/Foundry.Deploy.Tests/PrepareTargetDiskLayoutStepTests.cs index 59408b5f..b960b58c 100644 --- a/src/Foundry.Deploy.Tests/PrepareTargetDiskLayoutStepTests.cs +++ b/src/Foundry.Deploy.Tests/PrepareTargetDiskLayoutStepTests.cs @@ -78,6 +78,19 @@ public Task PrepareTargetDiskAsync( int diskNumber, string workingDirectory, CancellationToken cancellationToken = default) + { + return PrepareTargetDiskAsync( + diskNumber, + workingDirectory, + RecoveryTargetDiskLayoutMode.FullWipe, + cancellationToken); + } + + public Task PrepareTargetDiskAsync( + int diskNumber, + string workingDirectory, + RecoveryTargetDiskLayoutMode layoutMode, + CancellationToken cancellationToken = default) { Directory.CreateDirectory(workingDirectory); diff --git a/src/Foundry.Deploy.Tests/ProvisionOsRecoveryStepTests.cs b/src/Foundry.Deploy.Tests/ProvisionOsRecoveryStepTests.cs new file mode 100644 index 00000000..7d286af7 --- /dev/null +++ b/src/Foundry.Deploy.Tests/ProvisionOsRecoveryStepTests.cs @@ -0,0 +1,437 @@ +using Foundry.Core.Models.Configuration; +using Foundry.Core.Services.Configuration; +using Foundry.Core.Services.WinPe; +using Foundry.Core.Services.WinPe.OsRecovery; +using Foundry.Deploy.Models; +using Foundry.Deploy.Models.Configuration; +using Foundry.Deploy.Services.Configuration; +using Foundry.Deploy.Services.Deployment; +using Foundry.Deploy.Services.Deployment.Steps; +using Foundry.Deploy.Services.Hardware; +using Foundry.Deploy.Services.Logging; +using Foundry.Deploy.Services.Operations; +using Foundry.Deploy.Services.System; +using CoreDeployNetworkSettings = Foundry.Core.Models.Configuration.Deploy.DeployNetworkSettings; +using CoreDeployNetworkProfileRoamingSettings = Foundry.Core.Models.Configuration.Deploy.DeployNetworkProfileRoamingSettings; + +namespace Foundry.Deploy.Tests; + +public sealed class ProvisionOsRecoveryStepTests +{ + [Fact] + public async Task ExecuteAsync_WhenOsRecoveryIsDisabled_SkipsWithoutProvisioning() + { + using TempWorkspace workspace = TempWorkspace.Create(); + var provisioning = new RecordingOsRecoveryPayloadProvisioningService(); + ProvisionOsRecoveryStep step = CreateStep(provisioning); + DeploymentStepExecutionContext context = CreateContext(workspace.RootPath, isOsRecoveryEnabled: false); + + DeploymentStepResult result = await step.ExecuteAsync(context, TestContext.Current.CancellationToken); + + Assert.Equal(DeploymentStepState.Skipped, result.State); + Assert.Equal("OS Recovery is disabled.", result.Message); + Assert.Equal(0, provisioning.CallCount); + } + + [Fact] + public async Task ExecuteAsync_WhenRunningFromRecoveryMode_SkipsWithoutProvisioning() + { + using TempWorkspace workspace = TempWorkspace.Create(); + var provisioning = new RecordingOsRecoveryPayloadProvisioningService(); + ProvisionOsRecoveryStep step = CreateStep(provisioning); + DeploymentStepExecutionContext context = CreateContext( + workspace.RootPath, + isOsRecoveryEnabled: true, + mode: DeploymentMode.Recovery); + + DeploymentStepResult result = await step.ExecuteAsync(context, TestContext.Current.CancellationToken); + + Assert.Equal(DeploymentStepState.Skipped, result.State); + Assert.Equal("OS Recovery provisioning is skipped in recovery mode.", result.Message); + Assert.Equal(0, provisioning.CallCount); + } + + [Fact] + public async Task ExecuteAsync_WhenRecoveryPartitionIsUnavailable_FailsBeforeProvisioning() + { + using TempWorkspace workspace = TempWorkspace.Create(); + var provisioning = new RecordingOsRecoveryPayloadProvisioningService(); + ProvisionOsRecoveryStep step = CreateStep(provisioning); + DeploymentStepExecutionContext context = CreateContext( + workspace.RootPath, + isOsRecoveryEnabled: true, + mode: DeploymentMode.Iso); + + DeploymentStepResult result = await step.ExecuteAsync(context, TestContext.Current.CancellationToken); + + Assert.Equal(DeploymentStepState.Failed, result.State); + Assert.Equal("Recovery partition is unavailable.", result.Message); + Assert.Equal(0, provisioning.CallCount); + } + + [Fact] + public async Task ExecuteAsync_WhenDryRunAndEnabled_SucceedsWithoutProvisioning() + { + using TempWorkspace workspace = TempWorkspace.Create(); + var provisioning = new RecordingOsRecoveryPayloadProvisioningService(); + ProvisionOsRecoveryStep step = CreateStep(provisioning); + DeploymentStepExecutionContext context = CreateContext( + workspace.RootPath, + isOsRecoveryEnabled: true, + isDryRun: true); + + DeploymentStepResult result = await step.ExecuteAsync(context, TestContext.Current.CancellationToken); + + Assert.Equal(DeploymentStepState.Succeeded, result.State); + Assert.Equal("OS Recovery provisioned (simulation).", result.Message); + Assert.Equal(0, provisioning.CallCount); + } + + [Fact] + public async Task ExecuteAsync_WhenLiveAndEnabled_ProvisionsSanitizedRecoveryPayload() + { + using TempWorkspace workspace = TempWorkspace.Create(); + string windowsRoot = Path.Combine(workspace.RootPath, "W"); + string recoveryRoot = Path.Combine(workspace.RootPath, "R"); + Directory.CreateDirectory(Path.Combine(windowsRoot, "Windows")); + Directory.CreateDirectory(Path.Combine(recoveryRoot, "Recovery", "WindowsRE")); + await File.WriteAllTextAsync( + Path.Combine(recoveryRoot, "Recovery", "WindowsRE", "winre.wim"), + "wim", + TestContext.Current.CancellationToken); + + string deployConfigurationPath = Path.Combine(workspace.RootPath, "foundry.deploy.config.json"); + FoundryDeployConfigurationDocument sourceDocument = new() + { + OsRecovery = new DeployOsRecoverySettings + { + IsEnabled = true + }, + Autopilot = new DeployAutopilotSettings + { + IsEnabled = true, + DefaultProfileFolderName = "profile" + }, + Network = new CoreDeployNetworkSettings + { + ProfileRoaming = new CoreDeployNetworkProfileRoamingSettings + { + IsEnabled = true, + IncludePrivateKeyMaterial = true, + ArtifactRootPath = @"X:\Foundry\Config\Network" + } + } + }; + await File.WriteAllTextAsync( + deployConfigurationPath, + System.Text.Json.JsonSerializer.Serialize(sourceDocument, ConfigurationJsonDefaults.SerializerOptions), + TestContext.Current.CancellationToken); + + var provisioning = new RecordingOsRecoveryPayloadProvisioningService(); + var processRunner = new RecordingProcessRunner(); + ProvisionOsRecoveryStep step = new( + provisioning, + new FakeEmbeddedAssetService(), + new FakeDeployConfigurationService(deployConfigurationPath), + processRunner, + winReConfigToolPath: Path.Combine(workspace.RootPath, "winrecfg.exe")); + DeploymentStepExecutionContext context = CreateContext( + workspace.RootPath, + isOsRecoveryEnabled: true, + mode: DeploymentMode.Iso, + isDryRun: false, + runtimeState => + { + runtimeState.TargetWindowsPartitionRoot = windowsRoot; + runtimeState.TargetRecoveryPartitionRoot = recoveryRoot; + runtimeState.TargetFoundryRoot = Path.Combine(windowsRoot, "Foundry"); + runtimeState.WinReConfigured = true; + Directory.CreateDirectory(runtimeState.TargetFoundryRoot); + }); + + DeploymentStepResult result = await step.ExecuteAsync(context, TestContext.Current.CancellationToken); + + Assert.Equal(DeploymentStepState.Succeeded, result.State); + Assert.Equal(1, provisioning.CallCount); + Assert.Contains(processRunner.Calls, call => call.Contains("/Mount-Image", StringComparison.Ordinal)); + Assert.Contains(processRunner.Calls, call => call.Contains("/Unmount-Image", StringComparison.Ordinal) && call.Contains("/Commit", StringComparison.Ordinal)); + Assert.Contains(processRunner.Calls, call => call.Contains("/setbootshelllink", StringComparison.Ordinal)); + Assert.True(File.Exists(Path.Combine(recoveryRoot, "Recovery", "WindowsRE", "FoundryOsRecovery.json"))); + + FoundryDeployConfigurationDocument recoveryDeployDocument = + System.Text.Json.JsonSerializer.Deserialize( + provisioning.CapturedOptions!.DeployConfigurationJson, + ConfigurationJsonDefaults.SerializerOptions)!; + FoundryConnectConfigurationDocument recoveryConnectDocument = + System.Text.Json.JsonSerializer.Deserialize( + provisioning.CapturedOptions.FoundryConnectConfigurationJson, + ConfigurationJsonDefaults.SerializerOptions)!; + + Assert.False(recoveryDeployDocument.Autopilot.IsEnabled); + Assert.False(recoveryDeployDocument.Network.ProfileRoaming.IsEnabled); + Assert.False(recoveryConnectDocument.Wifi.IsEnabled); + Assert.False(recoveryConnectDocument.Dot1x.IsEnabled); + } + + private static ProvisionOsRecoveryStep CreateStep(RecordingOsRecoveryPayloadProvisioningService provisioning) + { + return new ProvisionOsRecoveryStep( + provisioning, + new FakeEmbeddedAssetService(), + new FakeDeployConfigurationService(), + new NoOpProcessRunner()); + } + + private static DeploymentStepExecutionContext CreateContext( + string workspaceRoot, + bool isOsRecoveryEnabled, + DeploymentMode mode = DeploymentMode.Iso, + bool isDryRun = false, + Action? configureRuntimeState = null) + { + DeploymentContext request = new() + { + Mode = mode, + CacheRootPath = workspaceRoot, + TargetDiskNumber = 1, + TargetComputerName = "LAB01", + OperatingSystem = new OperatingSystemCatalogItem + { + Architecture = "x64" + }, + DriverPackSelectionKind = DriverPackSelectionKind.None, + OsRecovery = new DeployOsRecoverySettings + { + IsEnabled = isOsRecoveryEnabled + }, + IsDryRun = isDryRun + }; + + DeploymentRuntimeState runtimeState = new() + { + WorkspaceRoot = workspaceRoot, + Mode = mode, + TargetDiskNumber = 1, + IsOsRecoveryEnabled = isOsRecoveryEnabled, + IsDryRun = isDryRun + }; + configureRuntimeState?.Invoke(runtimeState); + + return new DeploymentStepExecutionContext( + request, + runtimeState, + [DeploymentStepNames.ProvisionOsRecovery], + new FakeOperationProgressService(), + new FakeDeploymentLogService(), + new FakeTargetDiskService(), + _ => { }); + } + + private sealed class RecordingOsRecoveryPayloadProvisioningService : IOsRecoveryPayloadProvisioningService + { + public int CallCount { get; private set; } + public OsRecoveryPayloadProvisioningOptions? CapturedOptions { get; private set; } + + public Task> ProvisionAsync( + OsRecoveryPayloadProvisioningOptions options, + IProgress? downloadProgress = null, + CancellationToken cancellationToken = default) + { + CallCount++; + CapturedOptions = options; + return Task.FromResult(WinPeResult.Success(new OsRecoveryPayloadProvisioningResult + { + BootMenuConfigurationXml = "", + ManagedPayloadSizeBytes = 1 + })); + } + } + + private sealed class FakeEmbeddedAssetService : IWinPeEmbeddedAssetService + { + public string GetBootstrapScriptContent() => "bootstrap"; + public string GetUsbProvisioningScriptTemplateContent() => "usb"; + public string GetIanaWindowsTimeZoneMapJson() => "{}"; + public string GetSevenZipSourceDirectoryPath() => string.Empty; + } + + private sealed class FakeDeployConfigurationService : IDeployConfigurationService + { + private readonly string? _configurationPath; + + public FakeDeployConfigurationService(string? configurationPath = null) + { + _configurationPath = configurationPath; + } + + public DeployConfigurationLoadResult LoadOptional() + { + return string.IsNullOrWhiteSpace(_configurationPath) + ? new DeployConfigurationLoadResult() + : new DeployConfigurationLoadResult + { + Exists = true, + ConfigurationPath = _configurationPath + }; + } + } + + private sealed class NoOpProcessRunner : IProcessRunner + { + public Task RunAsync( + string fileName, + string arguments, + string workingDirectory, + CancellationToken cancellationToken = default) + { + return Task.FromResult(new ProcessExecutionResult { ExitCode = 0 }); + } + + public Task RunAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + CancellationToken cancellationToken = default) + { + return Task.FromResult(new ProcessExecutionResult { ExitCode = 0 }); + } + + public Task RunAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + Action? onOutputData, + Action? onErrorData, + CancellationToken cancellationToken = default) + { + return Task.FromResult(new ProcessExecutionResult { ExitCode = 0 }); + } + } + + private sealed class RecordingProcessRunner : IProcessRunner + { + public List Calls { get; } = []; + + public Task RunAsync( + string fileName, + string arguments, + string workingDirectory, + CancellationToken cancellationToken = default) + { + Calls.Add($"{fileName} {arguments}"); + return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, FileName = fileName, Arguments = arguments }); + } + + public Task RunAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + CancellationToken cancellationToken = default) + { + string joinedArguments = string.Join(' ', arguments); + Calls.Add($"{fileName} {joinedArguments}"); + return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, FileName = fileName, Arguments = joinedArguments }); + } + + public Task RunAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + Action? onOutputData, + Action? onErrorData, + CancellationToken cancellationToken = default) + { + return RunAsync(fileName, arguments, workingDirectory, cancellationToken); + } + } + + private sealed class FakeOperationProgressService : IOperationProgressService + { + public bool IsOperationInProgress => false; + public int Progress => 0; + public string? Status => null; + public OperationKind? CurrentOperation => null; + public bool CanStartOperation => true; + public event EventHandler? ProgressChanged; + public bool TryStart(OperationKind kind, string initialStatus, int initialProgress = 0) => true; + public void Report(int progress, string? status = null) => ProgressChanged?.Invoke(this, EventArgs.Empty); + public void Complete(string? status = null) => ProgressChanged?.Invoke(this, EventArgs.Empty); + public void Fail(string status) => ProgressChanged?.Invoke(this, EventArgs.Empty); + public void ResetToIdle() => ProgressChanged?.Invoke(this, EventArgs.Empty); + } + + private sealed class FakeDeploymentLogService : IDeploymentLogService + { + public DeploymentLogSession Initialize(string rootPath) + { + string logsDirectory = Path.Combine(rootPath, "Logs"); + string stateDirectory = Path.Combine(rootPath, "State"); + Directory.CreateDirectory(logsDirectory); + Directory.CreateDirectory(stateDirectory); + return new DeploymentLogSession + { + RootPath = rootPath, + LogsDirectoryPath = logsDirectory, + StateDirectoryPath = stateDirectory, + LogFilePath = Path.Combine(logsDirectory, "FoundryDeploy.log"), + StateFilePath = Path.Combine(stateDirectory, "deployment-state.json") + }; + } + + public Task AppendAsync( + DeploymentLogSession session, + DeploymentLogLevel level, + string message, + CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public Task SaveStateAsync( + DeploymentLogSession session, + TState state, + CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public void Release(DeploymentLogSession session) + { + } + } + + private sealed class FakeTargetDiskService : ITargetDiskService + { + public Task> GetDisksAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult>([]); + } + + public Task GetDiskNumberForPathAsync(string path, CancellationToken cancellationToken = default) + { + return Task.FromResult(null); + } + } + + private sealed class TempWorkspace : IDisposable + { + private TempWorkspace(string rootPath) + { + RootPath = rootPath; + } + + public string RootPath { get; } + + public static TempWorkspace Create() + { + string rootPath = Path.Combine(Path.GetTempPath(), $"foundry-osrecovery-step-{Guid.NewGuid():N}"); + Directory.CreateDirectory(rootPath); + return new TempWorkspace(rootPath); + } + + public void Dispose() + { + Directory.Delete(RootPath, recursive: true); + } + } +} diff --git a/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs b/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs new file mode 100644 index 00000000..5d2ef487 --- /dev/null +++ b/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs @@ -0,0 +1,72 @@ +using Foundry.Deploy.Services.Runtime; +using Foundry.Deploy.Services.System; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Foundry.Deploy.Tests; + +public sealed class RecoveryTargetDiskResolverTests +{ + [Fact] + public async Task ResolveAsync_WhenExactlyOneFoundryRecoveryMarkerExists_ReturnsDiskNumber() + { + var processRunner = new FixedOutputProcessRunner("""{"CandidateCount":1,"DiskNumber":2}"""); + var resolver = new RecoveryTargetDiskResolver(processRunner, NullLogger.Instance); + + int? diskNumber = await resolver.ResolveAsync(TestContext.Current.CancellationToken); + + Assert.Equal(2, diskNumber); + } + + [Theory] + [InlineData("""{"CandidateCount":0}""")] + [InlineData("""{"CandidateCount":2}""")] + [InlineData("")] + public async Task ResolveAsync_WhenFoundryRecoveryMarkerIsMissingOrAmbiguous_ReturnsNull(string output) + { + var processRunner = new FixedOutputProcessRunner(output); + var resolver = new RecoveryTargetDiskResolver(processRunner, NullLogger.Instance); + + int? diskNumber = await resolver.ResolveAsync(TestContext.Current.CancellationToken); + + Assert.Null(diskNumber); + } + + private sealed class FixedOutputProcessRunner(string standardOutput) : IProcessRunner + { + public Task RunAsync( + string fileName, + string arguments, + string workingDirectory, + CancellationToken cancellationToken = default) + { + return Task.FromResult(new ProcessExecutionResult + { + ExitCode = 0, + FileName = fileName, + Arguments = arguments, + WorkingDirectory = workingDirectory, + StandardOutput = standardOutput + }); + } + + public Task RunAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + CancellationToken cancellationToken = default) + { + return RunAsync(fileName, string.Join(' ', arguments), workingDirectory, cancellationToken); + } + + public Task RunAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + Action? onOutputData, + Action? onErrorData, + CancellationToken cancellationToken = default) + { + return RunAsync(fileName, arguments, workingDirectory, cancellationToken); + } + } +} diff --git a/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs b/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs index eecc2af2..fd5462ba 100644 --- a/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs +++ b/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs @@ -36,6 +36,37 @@ public async Task PrepareTargetDiskAsync_CreatesPartitionsInExpectedOrder_Efi_Ms Assert.DoesNotContain(scriptLines, line => line.StartsWith("shrink ", StringComparison.OrdinalIgnoreCase)); } + [Fact] + public async Task PrepareTargetDiskAsync_WhenRecoveryMode_PreservesRecoveryAndFormatsWindows() + { + using var workspace = new TemporaryWorkspace(); + string workingDirectory = Path.Combine(workspace.RootPath, "Work"); + var processRunner = new RecordingProcessRunner + { + PowerShellOutput = """ + {"SystemPartitionNumber":1,"RecoveryPartitionNumber":3,"WindowsPartitionNumber":4} + """ + }; + var service = new WindowsDeploymentService(processRunner, NullLogger.Instance); + + await service.PrepareTargetDiskAsync( + 1, + workingDirectory, + RecoveryTargetDiskLayoutMode.RecoveryRetrySafe, + TestContext.Current.CancellationToken); + + string scriptPath = Path.Combine(workingDirectory, "diskpart-os-target.txt"); + string[] scriptLines = await File.ReadAllLinesAsync(scriptPath, TestContext.Current.CancellationToken); + + Assert.DoesNotContain("clean", scriptLines); + Assert.DoesNotContain(scriptLines, line => line.Equals("delete partition override", StringComparison.OrdinalIgnoreCase)); + Assert.Contains("select partition 1", scriptLines); + Assert.Contains("select partition 3", scriptLines); + Assert.Contains("select partition 4", scriptLines); + Assert.Contains("format quick fs=ntfs label=Windows", scriptLines); + Assert.Contains("assign letter=R", scriptLines); + } + [Fact] public async Task ConfigureOfflineComputerNameAsync_WhenDefaultTimeZoneIdIsProvided_WritesUnattendTimeZone() { @@ -268,6 +299,8 @@ private sealed class RecordingProcessRunner : IProcessRunner { public List Calls { get; } = []; + public string PowerShellOutput { get; init; } = string.Empty; + public Task RunAsync( string fileName, string arguments, @@ -275,7 +308,7 @@ public Task RunAsync( CancellationToken cancellationToken = default) { Calls.Add($"{fileName} {arguments}"); - return Task.FromResult(new ProcessExecutionResult { ExitCode = 0 }); + return Task.FromResult(CreateResult(fileName)); } public Task RunAsync( @@ -285,7 +318,7 @@ public Task RunAsync( CancellationToken cancellationToken = default) { Calls.Add($"{fileName} {string.Join(' ', arguments)}"); - return Task.FromResult(new ProcessExecutionResult { ExitCode = 0 }); + return Task.FromResult(CreateResult(fileName)); } public Task RunAsync( @@ -297,7 +330,14 @@ public Task RunAsync( CancellationToken cancellationToken = default) { Calls.Add($"{fileName} {string.Join(' ', arguments)}"); - return Task.FromResult(new ProcessExecutionResult { ExitCode = 0 }); + return Task.FromResult(CreateResult(fileName)); + } + + private ProcessExecutionResult CreateResult(string fileName) + { + return string.Equals(fileName, "powershell.exe", StringComparison.OrdinalIgnoreCase) + ? new ProcessExecutionResult { ExitCode = 0, StandardOutput = PowerShellOutput } + : new ProcessExecutionResult { ExitCode = 0 }; } } } diff --git a/src/Foundry.Deploy/DependencyInjection/ServiceCollectionExtensions.cs b/src/Foundry.Deploy/DependencyInjection/ServiceCollectionExtensions.cs index bae634aa..50907b99 100644 --- a/src/Foundry.Deploy/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Foundry.Deploy/DependencyInjection/ServiceCollectionExtensions.cs @@ -23,6 +23,8 @@ using Foundry.Deploy.Services.Theme; using Foundry.Deploy.Services.Wizard; using Foundry.Deploy.ViewModels; +using Foundry.Core.Services.WinPe; +using Foundry.Core.Services.WinPe.OsRecovery; using Foundry.Telemetry; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -47,7 +49,10 @@ public static IServiceCollection AddFoundryDeployApplicationServices(this IServi services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(CreateTelemetryOptions); services.AddSingleton(CreateTelemetryContext); services.AddSingleton(sp => @@ -129,6 +134,7 @@ public static IServiceCollection AddFoundryDeployApplicationServices(this IServi services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Foundry.Deploy/Models/Configuration/DeployOsRecoverySettings.cs b/src/Foundry.Deploy/Models/Configuration/DeployOsRecoverySettings.cs new file mode 100644 index 00000000..1e73532b --- /dev/null +++ b/src/Foundry.Deploy/Models/Configuration/DeployOsRecoverySettings.cs @@ -0,0 +1,12 @@ +namespace Foundry.Deploy.Models.Configuration; + +/// +/// Describes whether Foundry.Deploy should provision the Foundry OS Recovery WinRE entry. +/// +public sealed record DeployOsRecoverySettings +{ + /// + /// Gets a value indicating whether OS Recovery provisioning is enabled. + /// + public bool IsEnabled { get; init; } +} diff --git a/src/Foundry.Deploy/Models/Configuration/FoundryDeployConfigurationDocument.cs b/src/Foundry.Deploy/Models/Configuration/FoundryDeployConfigurationDocument.cs index f2026f68..72ccb423 100644 --- a/src/Foundry.Deploy/Models/Configuration/FoundryDeployConfigurationDocument.cs +++ b/src/Foundry.Deploy/Models/Configuration/FoundryDeployConfigurationDocument.cs @@ -29,6 +29,11 @@ public sealed record FoundryDeployConfigurationDocument /// public DeployLocalizationSettings Localization { get; init; } = new(); + /// + /// Gets Windows RE OS recovery settings used during deployment. + /// + public DeployOsRecoverySettings OsRecovery { get; init; } = new(); + /// /// Gets network profile roaming settings used during deployment. /// diff --git a/src/Foundry.Deploy/Models/DeploymentMode.cs b/src/Foundry.Deploy/Models/DeploymentMode.cs index 3ab93c67..8a4a470b 100644 --- a/src/Foundry.Deploy/Models/DeploymentMode.cs +++ b/src/Foundry.Deploy/Models/DeploymentMode.cs @@ -3,5 +3,6 @@ namespace Foundry.Deploy.Models; public enum DeploymentMode { Usb, - Iso + Iso, + Recovery } diff --git a/src/Foundry.Deploy/Services/Cache/CacheLocatorService.cs b/src/Foundry.Deploy/Services/Cache/CacheLocatorService.cs index 83bb9b7e..f66b3687 100644 --- a/src/Foundry.Deploy/Services/Cache/CacheLocatorService.cs +++ b/src/Foundry.Deploy/Services/Cache/CacheLocatorService.cs @@ -29,6 +29,7 @@ public Task ResolveAsync( CacheResolution resolution = mode switch { DeploymentMode.Iso => ResolveIso(preferredRoot), + DeploymentMode.Recovery => ResolveIso(preferredRoot), _ => ResolveUsb(preferredRoot) }; diff --git a/src/Foundry.Deploy/Services/Deployment/DeploymentContext.cs b/src/Foundry.Deploy/Services/Deployment/DeploymentContext.cs index 65ef9a31..ff5d2135 100644 --- a/src/Foundry.Deploy/Services/Deployment/DeploymentContext.cs +++ b/src/Foundry.Deploy/Services/Deployment/DeploymentContext.cs @@ -74,6 +74,11 @@ public sealed record DeploymentContext /// public DeployAutopilotHardwareHashUploadSettings AutopilotHardwareHashUpload { get; init; } = new(); + /// + /// Gets Windows RE OS recovery settings used during deployment. + /// + public DeployOsRecoverySettings OsRecovery { get; init; } = new(); + /// /// Gets network settings used by late deployment steps. /// diff --git a/src/Foundry.Deploy/Services/Deployment/DeploymentLaunchPreparationService.cs b/src/Foundry.Deploy/Services/Deployment/DeploymentLaunchPreparationService.cs index 130f4b1f..9602d6c1 100644 --- a/src/Foundry.Deploy/Services/Deployment/DeploymentLaunchPreparationService.cs +++ b/src/Foundry.Deploy/Services/Deployment/DeploymentLaunchPreparationService.cs @@ -67,7 +67,7 @@ public DeploymentLaunchPreparationResult Prepare(DeploymentLaunchRequest request return DeploymentLaunchPreparationResult.Failure(normalizedComputerName); } - if (!request.IsDryRun && !ConfirmDestructiveDeployment(effectiveTargetDisk, request.SelectedOperatingSystem)) + if (!request.IsDryRun && !ConfirmDestructiveDeployment(request.Mode, effectiveTargetDisk, request.SelectedOperatingSystem)) { return DeploymentLaunchPreparationResult.Failure(normalizedComputerName); } @@ -87,6 +87,7 @@ public DeploymentLaunchPreparationResult Prepare(DeploymentLaunchRequest request AutopilotProvisioningMode = request.AutopilotProvisioningMode, SelectedAutopilotProfile = request.SelectedAutopilotProfile, AutopilotHardwareHashUpload = request.AutopilotHardwareHashUpload, + OsRecovery = request.OsRecovery, Network = request.Network, Oobe = request.Oobe, AppxRemoval = request.AppxRemoval, @@ -103,15 +104,32 @@ public DeploymentLaunchPreparationResult Prepare(DeploymentLaunchRequest request /// /// Shows the final warning that live deployments erase the selected target disk. /// + /// The deployment mode used to choose the warning text. /// The disk that will be repartitioned. /// The operating system image that will be applied. /// when the user confirms the destructive operation. - private bool ConfirmDestructiveDeployment(TargetDiskInfo targetDisk, OperatingSystemCatalogItem operatingSystem) + private bool ConfirmDestructiveDeployment( + DeploymentMode mode, + TargetDiskInfo targetDisk, + OperatingSystemCatalogItem operatingSystem) { string sizeGiB = targetDisk.SizeBytes > 0 ? $"{(targetDisk.SizeBytes / 1024d / 1024d / 1024d):0.0} GiB" : LocalizationText.GetString("Disk.UnknownSize"); + if (mode == DeploymentMode.Recovery) + { + string recoveryMessage = LocalizationText.Format( + "Launch.ConfirmOsRecoveryMessageFormat", + targetDisk.DiskNumber, + targetDisk.FriendlyName, + targetDisk.BusType, + sizeGiB, + operatingSystem.DisplayLabel); + + return _applicationShellService.ConfirmWarning(LocalizationText.GetString("Launch.ConfirmOsRecoveryTitle"), recoveryMessage); + } + string message = LocalizationText.Format( "Launch.ConfirmDiskEraseMessageFormat", targetDisk.DiskNumber, diff --git a/src/Foundry.Deploy/Services/Deployment/DeploymentLaunchRequest.cs b/src/Foundry.Deploy/Services/Deployment/DeploymentLaunchRequest.cs index 8e3b02db..06428458 100644 --- a/src/Foundry.Deploy/Services/Deployment/DeploymentLaunchRequest.cs +++ b/src/Foundry.Deploy/Services/Deployment/DeploymentLaunchRequest.cs @@ -19,6 +19,7 @@ public sealed record DeploymentLaunchRequest public required AutopilotProvisioningMode AutopilotProvisioningMode { get; init; } public required AutopilotProfileCatalogItem? SelectedAutopilotProfile { get; init; } public DeployAutopilotHardwareHashUploadSettings AutopilotHardwareHashUpload { get; init; } = new(); + public DeployOsRecoverySettings OsRecovery { get; init; } = new(); public CoreDeployNetworkSettings Network { get; init; } = new(); public DeployOobeSettings Oobe { get; init; } = new(); public DeployAppxRemovalSettings AppxRemoval { get; init; } = new(); diff --git a/src/Foundry.Deploy/Services/Deployment/DeploymentOrchestrator.cs b/src/Foundry.Deploy/Services/Deployment/DeploymentOrchestrator.cs index c285736b..a10912d4 100644 --- a/src/Foundry.Deploy/Services/Deployment/DeploymentOrchestrator.cs +++ b/src/Foundry.Deploy/Services/Deployment/DeploymentOrchestrator.cs @@ -122,6 +122,7 @@ await TrackDeploymentCompletedAsync( AutopilotHardwareHashUploadState = context.IsAutopilotEnabled && context.AutopilotProvisioningMode == AutopilotProvisioningMode.HardwareHashUpload ? AutopilotHardwareHashUploadState.Planned : AutopilotHardwareHashUploadState.NotPlanned, + IsOsRecoveryEnabled = context.OsRecovery.IsEnabled, Network = context.Network, Oobe = context.Oobe, AppxRemoval = context.AppxRemoval, @@ -291,6 +292,7 @@ private Task TrackDeploymentCompletedAsync( ["deploy_driver_pack_vendor"] = NormalizeTelemetryString(context.DriverPack?.Manufacturer, "none"), ["deploy_driver_pack_model"] = ResolveDriverPackCatalogModel(context.DriverPack), ["deploy_firmware_updates_enabled"] = context.ApplyFirmwareUpdates, + ["deploy_os_recovery_enabled"] = context.OsRecovery.IsEnabled, ["deploy_autopilot_enabled"] = context.IsAutopilotEnabled, ["deploy_autopilot_provisioning_mode"] = NormalizeTelemetryString(ResolveAutopilotProvisioningMode(context)), ["deploy_autopilot_hash_upload_state"] = NormalizeTelemetryString(runtimeState?.AutopilotHardwareHashUploadState.ToString()), diff --git a/src/Foundry.Deploy/Services/Deployment/DeploymentRuntimeState.cs b/src/Foundry.Deploy/Services/Deployment/DeploymentRuntimeState.cs index 9e2fd549..1d625bda 100644 --- a/src/Foundry.Deploy/Services/Deployment/DeploymentRuntimeState.cs +++ b/src/Foundry.Deploy/Services/Deployment/DeploymentRuntimeState.cs @@ -236,6 +236,11 @@ public sealed record DeploymentRuntimeState /// public string? AutopilotHardwareHashUploadMessage { get; set; } + /// + /// Gets or sets a value indicating whether Foundry OS Recovery WinRE provisioning is enabled. + /// + public bool IsOsRecoveryEnabled { get; set; } + /// /// Gets or sets the diagnostics directory retained under the target Windows Temp\Foundry tree. /// diff --git a/src/Foundry.Deploy/Services/Deployment/DeploymentStepExecutionContext.cs b/src/Foundry.Deploy/Services/Deployment/DeploymentStepExecutionContext.cs index f697c4e4..66916eb8 100644 --- a/src/Foundry.Deploy/Services/Deployment/DeploymentStepExecutionContext.cs +++ b/src/Foundry.Deploy/Services/Deployment/DeploymentStepExecutionContext.cs @@ -250,7 +250,7 @@ await _deploymentLogService return (null, DeploymentStepResult.Failed($"Target disk {Request.TargetDiskNumber} is no longer present.")); } - if (!selectedDisk.IsSelectable) + if (!selectedDisk.IsSelectable && RuntimeState.Mode != DeploymentMode.Recovery) { return (null, DeploymentStepResult.Failed( $"Target disk {Request.TargetDiskNumber} is blocked: {selectedDisk.SelectionWarning}")); @@ -512,7 +512,7 @@ private string EnsureCacheBaseRoot() private string ResolvePayloadCacheRoot(string payloadFolderName, long requiredBytes) { - if (RuntimeState.Mode == DeploymentMode.Iso && + if ((RuntimeState.Mode == DeploymentMode.Iso || RuntimeState.Mode == DeploymentMode.Recovery) && !string.IsNullOrWhiteSpace(RuntimeState.TargetFoundryRoot)) { return Path.Combine(RuntimeState.TargetFoundryRoot, CacheFolderName, payloadFolderName); diff --git a/src/Foundry.Deploy/Services/Deployment/DeploymentStepNames.cs b/src/Foundry.Deploy/Services/Deployment/DeploymentStepNames.cs index f99e32de..a1f40816 100644 --- a/src/Foundry.Deploy/Services/Deployment/DeploymentStepNames.cs +++ b/src/Foundry.Deploy/Services/Deployment/DeploymentStepNames.cs @@ -19,6 +19,7 @@ public static class DeploymentStepNames public const string ConfigureRecoveryEnvironment = "Configure recovery environment"; public const string StagePreOobeCustomization = "Stage pre-OOBE customization"; public const string ApplyDriverPack = "Apply driver pack"; + public const string ProvisionOsRecovery = "Provision OS Recovery"; public const string DownloadFirmwareUpdate = "Download firmware update"; public const string ApplyFirmwareUpdate = "Apply firmware update"; public const string SealRecoveryPartition = "Seal recovery partition"; @@ -44,6 +45,7 @@ public static class DeploymentStepNames ExtractDriverPack, StagePreOobeCustomization, ApplyDriverPack, + ProvisionOsRecovery, DownloadFirmwareUpdate, ApplyFirmwareUpdate, SealRecoveryPartition, diff --git a/src/Foundry.Deploy/Services/Deployment/IWindowsDeploymentService.cs b/src/Foundry.Deploy/Services/Deployment/IWindowsDeploymentService.cs index 914e938b..43849089 100644 --- a/src/Foundry.Deploy/Services/Deployment/IWindowsDeploymentService.cs +++ b/src/Foundry.Deploy/Services/Deployment/IWindowsDeploymentService.cs @@ -19,6 +19,20 @@ Task PrepareTargetDiskAsync( string workingDirectory, CancellationToken cancellationToken = default); + /// + /// Prepares the target disk with the requested layout strategy. + /// + /// The disk number to prepare. + /// The directory used for temporary scripts. + /// The layout strategy to apply. + /// A token used to cancel diskpart execution. + /// The resulting target partition layout. + Task PrepareTargetDiskAsync( + int diskNumber, + string workingDirectory, + RecoveryTargetDiskLayoutMode layoutMode, + CancellationToken cancellationToken = default); + /// /// Resolves the WIM/ESD image index matching a requested edition. /// diff --git a/src/Foundry.Deploy/Services/Deployment/RecoveryTargetDiskLayoutMode.cs b/src/Foundry.Deploy/Services/Deployment/RecoveryTargetDiskLayoutMode.cs new file mode 100644 index 00000000..70ab2c74 --- /dev/null +++ b/src/Foundry.Deploy/Services/Deployment/RecoveryTargetDiskLayoutMode.cs @@ -0,0 +1,7 @@ +namespace Foundry.Deploy.Services.Deployment; + +public enum RecoveryTargetDiskLayoutMode +{ + FullWipe, + RecoveryRetrySafe +} diff --git a/src/Foundry.Deploy/Services/Deployment/Steps/ApplyFirmwareUpdateStep.cs b/src/Foundry.Deploy/Services/Deployment/Steps/ApplyFirmwareUpdateStep.cs index 9410b8f0..42a4309d 100644 --- a/src/Foundry.Deploy/Services/Deployment/Steps/ApplyFirmwareUpdateStep.cs +++ b/src/Foundry.Deploy/Services/Deployment/Steps/ApplyFirmwareUpdateStep.cs @@ -12,7 +12,7 @@ public ApplyFirmwareUpdateStep(IWindowsDeploymentService windowsDeploymentServic _windowsDeploymentService = windowsDeploymentService; } - public override int Order => 16; + public override int Order => 17; public override string Name => DeploymentStepNames.ApplyFirmwareUpdate; diff --git a/src/Foundry.Deploy/Services/Deployment/Steps/DownloadFirmwareUpdateStep.cs b/src/Foundry.Deploy/Services/Deployment/Steps/DownloadFirmwareUpdateStep.cs index 2e87b7f5..ac8914e5 100644 --- a/src/Foundry.Deploy/Services/Deployment/Steps/DownloadFirmwareUpdateStep.cs +++ b/src/Foundry.Deploy/Services/Deployment/Steps/DownloadFirmwareUpdateStep.cs @@ -14,7 +14,7 @@ public DownloadFirmwareUpdateStep(IMicrosoftUpdateCatalogFirmwareService firmwar _firmwareService = firmwareService; } - public override int Order => 15; + public override int Order => 16; public override string Name => DeploymentStepNames.DownloadFirmwareUpdate; diff --git a/src/Foundry.Deploy/Services/Deployment/Steps/FinalizeDeploymentAndWriteLogsStep.cs b/src/Foundry.Deploy/Services/Deployment/Steps/FinalizeDeploymentAndWriteLogsStep.cs index 3e1f5c9e..efd014bd 100644 --- a/src/Foundry.Deploy/Services/Deployment/Steps/FinalizeDeploymentAndWriteLogsStep.cs +++ b/src/Foundry.Deploy/Services/Deployment/Steps/FinalizeDeploymentAndWriteLogsStep.cs @@ -6,7 +6,7 @@ namespace Foundry.Deploy.Services.Deployment.Steps; public sealed class FinalizeDeploymentAndWriteLogsStep : DeploymentStepBase { - public override int Order => 19; + public override int Order => 20; public override string Name => DeploymentStepNames.FinalizeDeploymentAndWriteLogs; @@ -98,6 +98,7 @@ private static async Task WriteDeploymentSummaryAsync( preOobeManifestPath = runtimeState.PreOobeManifestPath, preOobeScriptPaths = runtimeState.PreOobeScriptPaths, applyFirmwareUpdates = runtimeState.ApplyFirmwareUpdates, + osRecoveryEnabled = runtimeState.IsOsRecoveryEnabled, downloadedFirmwarePath = runtimeState.DownloadedFirmwarePath, extractedFirmwarePath = runtimeState.ExtractedFirmwarePath, firmwareUpdateId = runtimeState.FirmwareUpdateId, diff --git a/src/Foundry.Deploy/Services/Deployment/Steps/GatherDeploymentVariablesStep.cs b/src/Foundry.Deploy/Services/Deployment/Steps/GatherDeploymentVariablesStep.cs index 83c431d2..2317de13 100644 --- a/src/Foundry.Deploy/Services/Deployment/Steps/GatherDeploymentVariablesStep.cs +++ b/src/Foundry.Deploy/Services/Deployment/Steps/GatherDeploymentVariablesStep.cs @@ -38,6 +38,10 @@ private static Task AppendRunContextAsync(DeploymentStepExecutionContext context targetComputerName = context.Request.TargetComputerName, driverPackSelectionKind = context.Request.DriverPackSelectionKind.ToString(), applyFirmwareUpdates = context.Request.ApplyFirmwareUpdates, + osRecovery = new + { + isEnabled = context.Request.OsRecovery.IsEnabled + }, autopilot = new { isEnabled = context.Request.IsAutopilotEnabled, @@ -110,6 +114,7 @@ private static Task AppendRunContextAsync(DeploymentStepExecutionContext context context.RuntimeState.ExtractedFirmwarePath, context.RuntimeState.FirmwareUpdateId, context.RuntimeState.FirmwareUpdateTitle, + context.RuntimeState.IsOsRecoveryEnabled, context.RuntimeState.IsAutopilotEnabled, context.RuntimeState.SelectedAutopilotProfileFolderName, context.RuntimeState.SelectedAutopilotProfileDisplayName, diff --git a/src/Foundry.Deploy/Services/Deployment/Steps/PrepareTargetDiskLayoutStep.cs b/src/Foundry.Deploy/Services/Deployment/Steps/PrepareTargetDiskLayoutStep.cs index 43d9c4b4..6f65eb02 100644 --- a/src/Foundry.Deploy/Services/Deployment/Steps/PrepareTargetDiskLayoutStep.cs +++ b/src/Foundry.Deploy/Services/Deployment/Steps/PrepareTargetDiskLayoutStep.cs @@ -30,10 +30,15 @@ protected override async Task ExecuteLiveAsync(DeploymentS } context.EmitCurrentStepIndeterminate("Preparing target disk layout...", "Partitioning target disk..."); + RecoveryTargetDiskLayoutMode layoutMode = context.RuntimeState.Mode == DeploymentMode.Recovery + ? RecoveryTargetDiskLayoutMode.RecoveryRetrySafe + : RecoveryTargetDiskLayoutMode.FullWipe; + DeploymentTargetLayout layout = await _windowsDeploymentService .PrepareTargetDiskAsync( context.Request.TargetDiskNumber, workingDirectory, + layoutMode, cancellationToken) .ConfigureAwait(false); diff --git a/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionAutopilotStep.cs b/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionAutopilotStep.cs index 4bf27a1e..e64faf27 100644 --- a/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionAutopilotStep.cs +++ b/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionAutopilotStep.cs @@ -30,7 +30,7 @@ public ProvisionAutopilotStep( _interactiveRegistrationProvisioningService = interactiveRegistrationProvisioningService; } - public override int Order => 18; + public override int Order => 19; public override string Name => DeploymentStepNames.ProvisionAutopilot; diff --git a/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs b/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs new file mode 100644 index 00000000..87fdb940 --- /dev/null +++ b/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs @@ -0,0 +1,465 @@ +using System.Globalization; +using System.IO; +using System.Text.Json; +using Foundry.Core.Models.Configuration; +using Foundry.Core.Services.Configuration; +using Foundry.Core.Services.WinPe; +using Foundry.Core.Services.WinPe.OsRecovery; +using Foundry.Deploy.Models; +using Foundry.Deploy.Models.Configuration; +using Foundry.Deploy.Services.Configuration; +using Foundry.Deploy.Services.Localization; +using Foundry.Deploy.Services.Logging; +using Foundry.Deploy.Services.System; +using CoreDeployNetworkSettings = Foundry.Core.Models.Configuration.Deploy.DeployNetworkSettings; + +namespace Foundry.Deploy.Services.Deployment.Steps; + +public sealed class ProvisionOsRecoveryStep : DeploymentStepBase +{ + private const string ConnectConfigurationPath = @"X:\Foundry\Config\foundry.connect.config.json"; + private const string WinReImageFileName = "winre.wim"; + private const string RecoveryMarkerFileName = "FoundryOsRecovery.json"; + + private readonly IOsRecoveryPayloadProvisioningService _osRecoveryPayloadProvisioningService; + private readonly IWinPeEmbeddedAssetService _embeddedAssetService; + private readonly IDeployConfigurationService _deployConfigurationService; + private readonly IProcessRunner _processRunner; + private readonly string? _winReConfigToolPath; + + public ProvisionOsRecoveryStep( + IOsRecoveryPayloadProvisioningService osRecoveryPayloadProvisioningService, + IWinPeEmbeddedAssetService embeddedAssetService, + IDeployConfigurationService deployConfigurationService, + IProcessRunner processRunner) + : this( + osRecoveryPayloadProvisioningService, + embeddedAssetService, + deployConfigurationService, + processRunner, + winReConfigToolPath: null) + { + } + + internal ProvisionOsRecoveryStep( + IOsRecoveryPayloadProvisioningService osRecoveryPayloadProvisioningService, + IWinPeEmbeddedAssetService embeddedAssetService, + IDeployConfigurationService deployConfigurationService, + IProcessRunner processRunner, + string? winReConfigToolPath) + { + _osRecoveryPayloadProvisioningService = osRecoveryPayloadProvisioningService; + _embeddedAssetService = embeddedAssetService; + _deployConfigurationService = deployConfigurationService; + _processRunner = processRunner; + _winReConfigToolPath = winReConfigToolPath; + } + + public override int Order => 15; + + public override string Name => DeploymentStepNames.ProvisionOsRecovery; + + protected override async Task ExecuteLiveAsync( + DeploymentStepExecutionContext context, + CancellationToken cancellationToken) + { + if (!context.Request.OsRecovery.IsEnabled) + { + return DeploymentStepResult.Skipped("OS Recovery is disabled."); + } + + if (context.RuntimeState.Mode == DeploymentMode.Recovery) + { + return DeploymentStepResult.Skipped("OS Recovery provisioning is skipped in recovery mode."); + } + + if (string.IsNullOrWhiteSpace(context.RuntimeState.TargetWindowsPartitionRoot) || + string.IsNullOrWhiteSpace(context.RuntimeState.TargetRecoveryPartitionRoot) || + !context.RuntimeState.WinReConfigured) + { + return DeploymentStepResult.Failed("Recovery partition is unavailable."); + } + + string targetFoundryRoot = context.EnsureTargetFoundryRoot(); + string workingDirectory = Path.Combine(targetFoundryRoot, "Temp", "Deployment", "OsRecovery"); + string scratchDirectory = Path.Combine(targetFoundryRoot, "Temp", "Dism"); + string mountPath = Path.Combine(workingDirectory, "Mount-WindowsRE"); + string winReImagePath = ResolveWinReImagePath(context.RuntimeState.TargetRecoveryPartitionRoot); + string recoveryMarkerPath = ResolveRecoveryMarkerPath(context.RuntimeState.TargetRecoveryPartitionRoot); + string windowsPath = Path.Combine(context.RuntimeState.TargetWindowsPartitionRoot, "Windows"); + + Directory.CreateDirectory(workingDirectory); + Directory.CreateDirectory(scratchDirectory); + ResetDirectory(mountPath); + + string deployConfigurationJson = await ReadDeployConfigurationJsonAsync(cancellationToken).ConfigureAwait(false); + string connectConfigurationJson = await ReadRecoveryConnectConfigurationJsonAsync(cancellationToken).ConfigureAwait(false); + + context.EmitCurrentStepIndeterminate("Provisioning OS Recovery...", "Mounting WinRE..."); + await RunRequiredProcessAsync( + "dism.exe", + [ + "/Mount-Image", + $"/ImageFile:{winReImagePath}", + "/Index:1", + $"/MountDir:{mountPath}", + $"/ScratchDir:{scratchDirectory}" + ], + workingDirectory, + "Failed to mount the Windows RE image", + cancellationToken).ConfigureAwait(false); + + bool mounted = true; + bool shouldCommit = false; + bool markerWritten = false; + Exception? operationException = null; + try + { + context.EmitCurrentStepIndeterminate("Provisioning OS Recovery...", "Injecting OS Recovery payload..."); + WinPeResult provisioningResult = + await _osRecoveryPayloadProvisioningService.ProvisionAsync( + new OsRecoveryPayloadProvisioningOptions + { + MountedImagePath = mountPath, + WorkingDirectoryPath = Path.Combine(workingDirectory, "Payload"), + Architecture = ResolveArchitecture(context.Request.OperatingSystem.Architecture), + BootstrapScriptContent = _embeddedAssetService.GetBootstrapScriptContent(), + FoundryConnectConfigurationJson = connectConfigurationJson, + DeployConfigurationJson = deployConfigurationJson, + IanaWindowsTimeZoneMapJson = _embeddedAssetService.GetIanaWindowsTimeZoneMapJson(), + SevenZipSourceDirectoryPath = _embeddedAssetService.GetSevenZipSourceDirectoryPath(), + Connect = new WinPeRuntimePayloadApplicationOptions + { + IsEnabled = true, + ProvisioningSource = WinPeProvisioningSource.Release + }, + BootMenuLocalizations = CreateBootMenuLocalizations() + }, + cancellationToken: cancellationToken).ConfigureAwait(false); + + if (!provisioningResult.IsSuccess) + { + throw new InvalidOperationException(FormatDiagnostic(provisioningResult.Error)); + } + + shouldCommit = true; + + context.EmitCurrentStepIndeterminate("Provisioning OS Recovery...", "Committing WinRE changes..."); + await UnmountAsync(mountPath, workingDirectory, shouldCommit: true, cancellationToken).ConfigureAwait(false); + mounted = false; + + context.EmitCurrentStepIndeterminate("Provisioning OS Recovery...", "Registering OS Recovery boot menu..."); + string bootMenuConfigPath = Path.Combine(workingDirectory, "AddFoundryRecoveryToBootMenu.xml"); + await File.WriteAllTextAsync( + bootMenuConfigPath, + provisioningResult.Value!.BootMenuConfigurationXml, + cancellationToken).ConfigureAwait(false); + + await WriteRecoveryMarkerAsync(recoveryMarkerPath, context, provisioningResult.Value, cancellationToken).ConfigureAwait(false); + markerWritten = true; + + await RunRequiredProcessAsync( + ResolveRequiredWinReConfigToolPath(), + ["/setbootshelllink", "/configfile", bootMenuConfigPath, "/target", windowsPath], + workingDirectory, + "Failed to register the OS Recovery boot menu link", + cancellationToken).ConfigureAwait(false); + + await context.AppendLogAsync( + DeploymentLogLevel.Info, + $"OS Recovery provisioned. ManagedPayloadSizeBytes={provisioningResult.Value.ManagedPayloadSizeBytes}.", + cancellationToken).ConfigureAwait(false); + + return DeploymentStepResult.Succeeded("OS Recovery provisioned."); + } + catch (Exception ex) + { + operationException = ex; + if (markerWritten) + { + TryDeleteFile(recoveryMarkerPath); + } + + throw; + } + finally + { + if (mounted) + { + try + { + await UnmountAsync(mountPath, workingDirectory, shouldCommit, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception unmountException) when (operationException is not null) + { + await TryAppendCleanupFailureLogAsync(context, unmountException).ConfigureAwait(false); + } + } + + TryDeleteDirectory(mountPath); + } + } + + protected override async Task ExecuteDryRunAsync( + DeploymentStepExecutionContext context, + CancellationToken cancellationToken) + { + if (!context.Request.OsRecovery.IsEnabled) + { + return DeploymentStepResult.Skipped("OS Recovery is disabled."); + } + + if (context.RuntimeState.Mode == DeploymentMode.Recovery) + { + return DeploymentStepResult.Skipped("OS Recovery provisioning is skipped in recovery mode."); + } + + context.EmitCurrentStepIndeterminate("Provisioning OS Recovery...", "Injecting OS Recovery payload..."); + await context.AppendLogAsync( + DeploymentLogLevel.Info, + "[DRY-RUN] Simulated OS Recovery WinRE payload provisioning.", + cancellationToken).ConfigureAwait(false); + await Task.Delay(150, cancellationToken).ConfigureAwait(false); + + return DeploymentStepResult.Succeeded("OS Recovery provisioned (simulation)."); + } + + private async Task ReadDeployConfigurationJsonAsync(CancellationToken cancellationToken) + { + DeployConfigurationLoadResult loadResult = _deployConfigurationService.LoadOptional(); + if (!loadResult.Exists || string.IsNullOrWhiteSpace(loadResult.ConfigurationPath) || !File.Exists(loadResult.ConfigurationPath)) + { + throw new FileNotFoundException("The Foundry.Deploy configuration file is required for OS Recovery provisioning.", loadResult.ConfigurationPath); + } + + await using FileStream stream = File.OpenRead(loadResult.ConfigurationPath); + FoundryDeployConfigurationDocument? document = await JsonSerializer.DeserializeAsync( + stream, + ConfigurationJsonDefaults.SerializerOptions, + cancellationToken).ConfigureAwait(false); + + if (document is null) + { + throw new InvalidOperationException("The Foundry.Deploy configuration file could not be parsed for OS Recovery provisioning."); + } + + FoundryDeployConfigurationDocument recoveryDocument = document with + { + Autopilot = new DeployAutopilotSettings(), + Network = new CoreDeployNetworkSettings() + }; + + return JsonSerializer.Serialize(recoveryDocument, ConfigurationJsonDefaults.SerializerOptions); + } + + private static async Task ReadRecoveryConnectConfigurationJsonAsync(CancellationToken cancellationToken) + { + if (!File.Exists(ConnectConfigurationPath)) + { + return JsonSerializer.Serialize(new FoundryConnectConfigurationDocument(), ConfigurationJsonDefaults.SerializerOptions); + } + + await using FileStream stream = File.OpenRead(ConnectConfigurationPath); + FoundryConnectConfigurationDocument? document = await JsonSerializer.DeserializeAsync( + stream, + ConfigurationJsonDefaults.SerializerOptions, + cancellationToken).ConfigureAwait(false); + + document ??= new FoundryConnectConfigurationDocument(); + FoundryConnectConfigurationDocument recoveryDocument = new() + { + SchemaVersion = document.SchemaVersion, + InternetProbe = document.InternetProbe, + Telemetry = document.Telemetry + }; + + return JsonSerializer.Serialize(recoveryDocument, ConfigurationJsonDefaults.SerializerOptions); + } + + private static IReadOnlyList CreateBootMenuLocalizations() + { + return new EmbeddedLanguageRegistryService() + .GetLanguages() + .Select(language => + { + CultureInfo culture = CultureInfo.GetCultureInfo(language.Code); + return new OsRecoveryBootMenuLocalization + { + Culture = language.Code, + Name = LocalizationText.ResourceManager.GetString("OsRecovery.BootMenuName", culture) ?? "Foundry Recovery", + Description = LocalizationText.ResourceManager.GetString("OsRecovery.BootMenuDescription", culture) ?? "Redeploy Windows" + }; + }) + .ToArray(); + } + + private async Task UnmountAsync( + string mountPath, + string workingDirectory, + bool shouldCommit, + CancellationToken cancellationToken) + { + await RunRequiredProcessAsync( + "dism.exe", + shouldCommit + ? ["/Unmount-Image", $"/MountDir:{mountPath}", "/Commit"] + : ["/Unmount-Image", $"/MountDir:{mountPath}", "/Discard"], + workingDirectory, + shouldCommit + ? "Failed to commit the Windows RE image" + : "Failed to discard the Windows RE image", + cancellationToken).ConfigureAwait(false); + } + + private static async Task TryAppendCleanupFailureLogAsync( + DeploymentStepExecutionContext context, + Exception exception) + { + try + { + await context.AppendLogAsync( + DeploymentLogLevel.Warning, + $"Failed to clean up mounted WinRE image after OS Recovery provisioning failure. {exception.Message}", + CancellationToken.None).ConfigureAwait(false); + } + catch + { + } + } + + private async Task RunRequiredProcessAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + string failureMessage, + CancellationToken cancellationToken) + { + ProcessExecutionResult result = await _processRunner + .RunAsync(fileName, arguments, workingDirectory, cancellationToken) + .ConfigureAwait(false); + + if (result.IsSuccess) + { + return; + } + + throw new InvalidOperationException($"{failureMessage}.{Environment.NewLine}{ToDiagnostic(result)}"); + } + + private static WinPeArchitecture ResolveArchitecture(string? architecture) + { + return !string.IsNullOrWhiteSpace(architecture) && + architecture.Contains("arm", StringComparison.OrdinalIgnoreCase) + ? WinPeArchitecture.Arm64 + : WinPeArchitecture.X64; + } + + private static string ResolveWinReImagePath(string recoveryPartitionRoot) + { + return Path.Combine(recoveryPartitionRoot, "Recovery", "WindowsRE", WinReImageFileName); + } + + private static string ResolveRecoveryMarkerPath(string recoveryPartitionRoot) + { + return Path.Combine(recoveryPartitionRoot, "Recovery", "WindowsRE", RecoveryMarkerFileName); + } + + private static Task WriteRecoveryMarkerAsync( + string markerPath, + DeploymentStepExecutionContext context, + OsRecoveryPayloadProvisioningResult provisioningResult, + CancellationToken cancellationToken) + { + Directory.CreateDirectory(Path.GetDirectoryName(markerPath)!); + var marker = new + { + schemaVersion = 1, + product = "Foundry OS Recovery", + createdUtc = DateTimeOffset.UtcNow, + targetDiskNumber = context.RuntimeState.TargetDiskNumber, + managedPayloadSizeBytes = provisioningResult.ManagedPayloadSizeBytes + }; + + string json = JsonSerializer.Serialize(marker, ConfigurationJsonDefaults.SerializerOptions); + return File.WriteAllTextAsync(markerPath, json, cancellationToken); + } + + private string ResolveRequiredWinReConfigToolPath() + { + if (!string.IsNullOrWhiteSpace(_winReConfigToolPath)) + { + return _winReConfigToolPath; + } + + string path = Path.Combine(Environment.SystemDirectory, "winrecfg.exe"); + if (!File.Exists(path)) + { + throw new FileNotFoundException( + "Required WinPE executable 'winrecfg.exe' was not found. Add the WinPE-WinReCfg optional component to the WinPE image.", + path); + } + + return path; + } + + private static void ResetDirectory(string path) + { + TryDeleteDirectory(path); + Directory.CreateDirectory(path); + } + + private static void TryDeleteDirectory(string path) + { + try + { + if (Directory.Exists(path)) + { + Directory.Delete(path, recursive: true); + } + } + catch + { + } + } + + private static void TryDeleteFile(string path) + { + try + { + if (File.Exists(path)) + { + File.Delete(path); + } + } + catch + { + } + } + + private static string FormatDiagnostic(WinPeDiagnostic? diagnostic) + { + if (diagnostic is null) + { + return "OS Recovery payload provisioning failed."; + } + + return string.IsNullOrWhiteSpace(diagnostic.Details) + ? diagnostic.Message + : $"{diagnostic.Message}{Environment.NewLine}{diagnostic.Details}"; + } + + private static string ToDiagnostic(ProcessExecutionResult result) + { + string stdout = string.IsNullOrWhiteSpace(result.StandardOutput) ? string.Empty : result.StandardOutput.Trim(); + string stderr = string.IsNullOrWhiteSpace(result.StandardError) ? string.Empty : result.StandardError.Trim(); + + return string.Join( + Environment.NewLine, + [ + $"Command: {result.FileName} {result.Arguments}", + $"ExitCode: {result.ExitCode}", + stdout.Length == 0 ? "StdOut: " : $"StdOut: {stdout}", + stderr.Length == 0 ? "StdErr: " : $"StdErr: {stderr}" + ]); + } +} diff --git a/src/Foundry.Deploy/Services/Deployment/Steps/SealRecoveryPartitionStep.cs b/src/Foundry.Deploy/Services/Deployment/Steps/SealRecoveryPartitionStep.cs index 13559757..80eadd6e 100644 --- a/src/Foundry.Deploy/Services/Deployment/Steps/SealRecoveryPartitionStep.cs +++ b/src/Foundry.Deploy/Services/Deployment/Steps/SealRecoveryPartitionStep.cs @@ -12,7 +12,7 @@ public SealRecoveryPartitionStep(IWindowsDeploymentService windowsDeploymentServ _windowsDeploymentService = windowsDeploymentService; } - public override int Order => 17; + public override int Order => 18; public override string Name => DeploymentStepNames.SealRecoveryPartition; diff --git a/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs b/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs index c68e5d88..578fce85 100644 --- a/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs +++ b/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; using System.Xml.Linq; using Foundry.Deploy.Models.Configuration; @@ -46,12 +48,31 @@ public async Task PrepareTargetDiskAsync( int diskNumber, string workingDirectory, CancellationToken cancellationToken = default) + { + return await PrepareTargetDiskAsync( + diskNumber, + workingDirectory, + RecoveryTargetDiskLayoutMode.FullWipe, + cancellationToken).ConfigureAwait(false); + } + + /// + public async Task PrepareTargetDiskAsync( + int diskNumber, + string workingDirectory, + RecoveryTargetDiskLayoutMode layoutMode, + CancellationToken cancellationToken = default) { if (diskNumber < 0) { throw new ArgumentOutOfRangeException(nameof(diskNumber), "Target disk number must be 0 or greater."); } + if (layoutMode == RecoveryTargetDiskLayoutMode.RecoveryRetrySafe) + { + return await PrepareRecoveryRetrySafeTargetDiskAsync(diskNumber, workingDirectory, cancellationToken).ConfigureAwait(false); + } + _logger.LogInformation( "Preparing target disk layout. DiskNumber={DiskNumber}, RecoveryPartitionSizeMb={RecoveryPartitionSizeMb}, WorkingDirectory={WorkingDirectory}", diskNumber, @@ -113,6 +134,120 @@ await RunRequiredProcessAsync( }; } + private async Task PrepareRecoveryRetrySafeTargetDiskAsync( + int diskNumber, + string workingDirectory, + CancellationToken cancellationToken) + { + Directory.CreateDirectory(workingDirectory); + (char systemLetter, char windowsLetter, char recoveryLetter) = GetPartitionLetters(); + + RecoveryDiskLayoutProbe probe = await ProbeRecoveryDiskLayoutAsync(diskNumber, workingDirectory, cancellationToken).ConfigureAwait(false); + + string[] scriptLines = + [ + $"select disk {diskNumber}", + "online disk noerr", + "attributes disk clear readonly noerr", + $"select partition {probe.SystemPartitionNumber}", + $"assign letter={systemLetter}", + $"select partition {probe.RecoveryPartitionNumber}", + $"assign letter={recoveryLetter}", + $"select partition {probe.WindowsPartitionNumber}", + "format quick fs=ntfs label=Windows", + $"assign letter={windowsLetter}" + ]; + + string scriptPath = Path.Combine(workingDirectory, "diskpart-os-target.txt"); + await File.WriteAllLinesAsync(scriptPath, scriptLines, cancellationToken).ConfigureAwait(false); + + await RunRequiredProcessAsync( + "diskpart.exe", + $"/s \"{scriptPath}\"", + workingDirectory, + $"Recovery disk preparation failed for disk {diskNumber}", + cancellationToken).ConfigureAwait(false); + + return new DeploymentTargetLayout + { + DiskNumber = diskNumber, + SystemPartitionRoot = $"{systemLetter}:\\", + WindowsPartitionRoot = $"{windowsLetter}:\\", + RecoveryPartitionRoot = $"{recoveryLetter}:\\", + RecoveryPartitionLetter = recoveryLetter + }; + } + + private async Task ProbeRecoveryDiskLayoutAsync( + int diskNumber, + string workingDirectory, + CancellationToken cancellationToken) + { + string script = $$""" +$ErrorActionPreference = 'Stop' +$diskNumber = {{diskNumber}} +$recoveryGuid = '{{RecoveryPartitionGuid}}' +$efiGuid = 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b' + +$partitions = Get-Partition -DiskNumber $diskNumber | Sort-Object -Property PartitionNumber +$system = $partitions | Where-Object { $_.GptType -and ([string]$_.GptType).Trim('{}').ToLowerInvariant() -eq $efiGuid } | Select-Object -First 1 +$recovery = $partitions | Where-Object { $_.GptType -and ([string]$_.GptType).Trim('{}').ToLowerInvariant() -eq $recoveryGuid } | Select-Object -First 1 + +if ($null -eq $system) { + throw "The target disk does not contain an EFI system partition." +} + +if ($null -eq $recovery) { + throw "The target disk does not contain the active OS Recovery partition." +} + +$windows = $partitions | + Where-Object { $_.PartitionNumber -gt $recovery.PartitionNumber -and $_.Type -eq 'Basic' } | + Select-Object -First 1 + +if ($null -eq $windows) { + throw "The target disk does not contain a Windows partition after the Recovery partition." +} + +[pscustomobject]@{ + SystemPartitionNumber = [int]$system.PartitionNumber + RecoveryPartitionNumber = [int]$recovery.PartitionNumber + WindowsPartitionNumber = [int]$windows.PartitionNumber +} | ConvertTo-Json -Compress +"""; + + string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(script)); + ProcessExecutionResult execution = await _processRunner + .RunAsync( + "powershell.exe", + $"-NoProfile -ExecutionPolicy Bypass -EncodedCommand {encoded}", + workingDirectory, + cancellationToken) + .ConfigureAwait(false); + + if (!execution.IsSuccess || string.IsNullOrWhiteSpace(execution.StandardOutput)) + { + throw new InvalidOperationException( + $"Unable to validate recovery disk layout for disk {diskNumber}.{Environment.NewLine}{ToDiagnostic(execution)}"); + } + + RecoveryDiskLayoutProbe? probe = JsonSerializer.Deserialize( + execution.StandardOutput, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + if (probe is null || + probe.SystemPartitionNumber <= 0 || + probe.RecoveryPartitionNumber <= 0 || + probe.WindowsPartitionNumber <= 0 || + probe.RecoveryPartitionNumber >= probe.WindowsPartitionNumber) + { + throw new InvalidOperationException( + $"The target disk layout is not supported for OS Recovery. Expected EFI, MSR, Recovery, Windows on disk {diskNumber}."); + } + + return probe; + } + /// public async Task ResolveImageIndexAsync( string imagePath, @@ -1100,4 +1235,11 @@ private sealed record ImageIndexDescriptor public required string Edition { get; init; } public required string EditionId { get; init; } } + + private sealed record RecoveryDiskLayoutProbe + { + public int SystemPartitionNumber { get; init; } + public int RecoveryPartitionNumber { get; init; } + public int WindowsPartitionNumber { get; init; } + } } diff --git a/src/Foundry.Deploy/Services/Localization/DeploymentUiTextLocalizer.cs b/src/Foundry.Deploy/Services/Localization/DeploymentUiTextLocalizer.cs index 3a87aa88..715c2ff8 100644 --- a/src/Foundry.Deploy/Services/Localization/DeploymentUiTextLocalizer.cs +++ b/src/Foundry.Deploy/Services/Localization/DeploymentUiTextLocalizer.cs @@ -30,6 +30,7 @@ public static string LocalizeStepName(string value) "Extract driver pack" => LocalizationText.GetString("Step.ExtractDriverPack"), "Stage pre-OOBE customization" => LocalizationText.GetString("Step.StagePreOobeCustomization"), "Apply driver pack" => LocalizationText.GetString("Step.ApplyDriverPack"), + "Provision OS Recovery" => LocalizationText.GetString("Step.ProvisionOsRecovery"), "Download firmware update" => LocalizationText.GetString("Step.DownloadFirmwareUpdate"), "Apply firmware update" => LocalizationText.GetString("Step.ApplyFirmwareUpdate"), "Seal recovery partition" => LocalizationText.GetString("Step.SealRecoveryPartition"), @@ -170,6 +171,14 @@ public static string LocalizeMessage(string value) "Driver pack applied." => LocalizationText.GetString("StepResult.DriverPackApplied"), "Driver pack staged for first boot." => LocalizationText.GetString("StepResult.DriverPackStagedForFirstBoot"), "Driver pack applied (simulation)." => LocalizationText.GetString("StepResult.DriverPackAppliedSimulation"), + "OS Recovery is disabled." => LocalizationText.GetString("StepResult.OsRecoveryDisabled"), + "OS Recovery provisioning is skipped in recovery mode." => LocalizationText.GetString("StepResult.OsRecoverySkippedInRecoveryMode"), + "Provisioning OS Recovery..." => LocalizationText.GetString("StepMessage.ProvisioningOsRecovery"), + "Injecting OS Recovery payload..." => LocalizationText.GetString("StepMessage.InjectingOsRecoveryPayload"), + "Committing WinRE changes..." => LocalizationText.GetString("StepMessage.CommittingWinReChanges"), + "Registering OS Recovery boot menu..." => LocalizationText.GetString("StepMessage.RegisteringOsRecoveryBootMenu"), + "OS Recovery provisioned." => LocalizationText.GetString("StepResult.OsRecoveryProvisioned"), + "OS Recovery provisioned (simulation)." => LocalizationText.GetString("StepResult.OsRecoveryProvisionedSimulation"), "Downloading firmware update..." => LocalizationText.GetString("StepMessage.DownloadingFirmwareUpdate"), "Preparing Microsoft Update Catalog lookup..." => LocalizationText.GetString("StepMessage.PreparingMicrosoftUpdateCatalogLookup"), "Firmware updates are disabled." => LocalizationText.GetString("StepResult.FirmwareUpdatesDisabled"), diff --git a/src/Foundry.Deploy/Services/Runtime/DeploymentRuntimeContextService.cs b/src/Foundry.Deploy/Services/Runtime/DeploymentRuntimeContextService.cs index dd44bef5..20d00706 100644 --- a/src/Foundry.Deploy/Services/Runtime/DeploymentRuntimeContextService.cs +++ b/src/Foundry.Deploy/Services/Runtime/DeploymentRuntimeContextService.cs @@ -39,10 +39,11 @@ private static bool TryResolveDeploymentModeFromEnvironment(out DeploymentMode m { "usb" => DeploymentMode.Usb, "iso" => DeploymentMode.Iso, + "recovery" => DeploymentMode.Recovery, _ => default }; - return normalized is "usb" or "iso"; + return normalized is "usb" or "iso" or "recovery"; } private static string? TryGetUsbCacheRuntimeRoot() diff --git a/src/Foundry.Deploy/Services/Runtime/IRecoveryTargetDiskResolver.cs b/src/Foundry.Deploy/Services/Runtime/IRecoveryTargetDiskResolver.cs new file mode 100644 index 00000000..3eb72e99 --- /dev/null +++ b/src/Foundry.Deploy/Services/Runtime/IRecoveryTargetDiskResolver.cs @@ -0,0 +1,6 @@ +namespace Foundry.Deploy.Services.Runtime; + +public interface IRecoveryTargetDiskResolver +{ + Task ResolveAsync(CancellationToken cancellationToken = default); +} diff --git a/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs b/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs new file mode 100644 index 00000000..db3c8e57 --- /dev/null +++ b/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs @@ -0,0 +1,126 @@ +using System.IO; +using System.Text; +using System.Text.Json; +using Foundry.Deploy.Services.System; +using Microsoft.Extensions.Logging; + +namespace Foundry.Deploy.Services.Runtime; + +public sealed class RecoveryTargetDiskResolver : IRecoveryTargetDiskResolver +{ + private const string RecoveryPartitionGuid = "de94bba4-06d1-4d40-a16a-bfd50179d6ac"; + private const string RecoveryMarkerRelativePath = @"Recovery\WindowsRE\FoundryOsRecovery.json"; + private readonly IProcessRunner _processRunner; + private readonly ILogger _logger; + + public RecoveryTargetDiskResolver(IProcessRunner processRunner, ILogger logger) + { + _processRunner = processRunner; + _logger = logger; + } + + public async Task ResolveAsync(CancellationToken cancellationToken = default) + { + string script = $$""" +$ErrorActionPreference = 'Stop' +$recoveryGuid = '{{RecoveryPartitionGuid}}' +$markerRelativePath = '{{RecoveryMarkerRelativePath}}' +$assignedAccessPaths = @() +$recoveryPartitions = Get-Partition | + Where-Object { $_.GptType -and ([string]$_.GptType).Trim('{}').ToLowerInvariant() -eq $recoveryGuid } | + Sort-Object -Property DiskNumber, PartitionNumber + +try { + $candidates = @() + foreach ($partition in $recoveryPartitions) { + $accessPath = @($partition.AccessPaths | Where-Object { $_ -match '^[A-Z]:\\$' } | Select-Object -First 1)[0] + if ([string]::IsNullOrWhiteSpace($accessPath)) { + Add-PartitionAccessPath -DiskNumber $partition.DiskNumber -PartitionNumber $partition.PartitionNumber -AssignDriveLetter | Out-Null + $partition = Get-Partition -DiskNumber $partition.DiskNumber -PartitionNumber $partition.PartitionNumber + $accessPath = @($partition.AccessPaths | Where-Object { $_ -match '^[A-Z]:\\$' } | Select-Object -First 1)[0] + if (-not [string]::IsNullOrWhiteSpace($accessPath)) { + $assignedAccessPaths += [pscustomobject]@{ + DiskNumber = [int]$partition.DiskNumber + PartitionNumber = [int]$partition.PartitionNumber + AccessPath = $accessPath + } + } + } + + if ([string]::IsNullOrWhiteSpace($accessPath)) { + continue + } + + $markerPath = Join-Path -Path $accessPath -ChildPath $markerRelativePath + if (Test-Path -LiteralPath $markerPath) { + $candidates += [pscustomobject]@{ + DiskNumber = [int]$partition.DiskNumber + PartitionNumber = [int]$partition.PartitionNumber + MarkerPath = $markerPath + } + } + } + + if ($candidates.Count -eq 1) { + [pscustomobject]@{ + CandidateCount = [int]$candidates.Count + DiskNumber = [int]$candidates[0].DiskNumber + } | ConvertTo-Json -Compress + return + } + + [pscustomobject]@{ + CandidateCount = [int]$candidates.Count + } | ConvertTo-Json -Compress +} +finally { + foreach ($assigned in $assignedAccessPaths) { + Remove-PartitionAccessPath -DiskNumber $assigned.DiskNumber -PartitionNumber $assigned.PartitionNumber -AccessPath $assigned.AccessPath -ErrorAction SilentlyContinue + } +} +"""; + + string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(script)); + ProcessExecutionResult execution = await _processRunner + .RunAsync( + "powershell.exe", + $"-NoProfile -ExecutionPolicy Bypass -EncodedCommand {encoded}", + Path.GetTempPath(), + cancellationToken) + .ConfigureAwait(false); + + if (!execution.IsSuccess || string.IsNullOrWhiteSpace(execution.StandardOutput)) + { + _logger.LogWarning("Unable to resolve active OS Recovery target disk. ExitCode={ExitCode}", execution.ExitCode); + return null; + } + + try + { + using JsonDocument document = JsonDocument.Parse(execution.StandardOutput); + int candidateCount = 0; + if (document.RootElement.TryGetProperty("CandidateCount", out JsonElement candidateCountElement)) + { + candidateCountElement.TryGetInt32(out candidateCount); + } + + if (candidateCount != 1) + { + _logger.LogWarning("OS Recovery target disk resolver found {CandidateCount} Foundry recovery partition marker(s).", candidateCount); + return null; + } + + if (document.RootElement.TryGetProperty("DiskNumber", out JsonElement diskNumberElement) && + diskNumberElement.TryGetInt32(out int diskNumber)) + { + return diskNumber; + } + } + catch (JsonException ex) + { + _logger.LogWarning(ex, "Failed to parse OS Recovery target disk resolver output."); + } + + return null; + } +} diff --git a/src/Foundry.Deploy/Services/Startup/DeploymentStartupCoordinator.cs b/src/Foundry.Deploy/Services/Startup/DeploymentStartupCoordinator.cs index 0e5ed8fa..9381bd7c 100644 --- a/src/Foundry.Deploy/Services/Startup/DeploymentStartupCoordinator.cs +++ b/src/Foundry.Deploy/Services/Startup/DeploymentStartupCoordinator.cs @@ -24,6 +24,7 @@ public sealed class DeploymentStartupCoordinator : IDeploymentStartupCoordinator private readonly ITargetDiskService _targetDiskService; private readonly IDeploymentCatalogLoadService _deploymentCatalogLoadService; private readonly IAutopilotGroupTagDiscoveryService _autopilotGroupTagDiscoveryService; + private readonly IRecoveryTargetDiskResolver _recoveryTargetDiskResolver; private readonly ILogger _logger; public DeploymentStartupCoordinator( @@ -34,6 +35,7 @@ public DeploymentStartupCoordinator( ITargetDiskService targetDiskService, IDeploymentCatalogLoadService deploymentCatalogLoadService, IAutopilotGroupTagDiscoveryService autopilotGroupTagDiscoveryService, + IRecoveryTargetDiskResolver recoveryTargetDiskResolver, ILogger logger) { _deployConfigurationService = deployConfigurationService; @@ -43,6 +45,7 @@ public DeploymentStartupCoordinator( _targetDiskService = targetDiskService; _deploymentCatalogLoadService = deploymentCatalogLoadService; _autopilotGroupTagDiscoveryService = autopilotGroupTagDiscoveryService; + _recoveryTargetDiskResolver = recoveryTargetDiskResolver; _logger = logger; } @@ -59,7 +62,7 @@ public async Task InitializeAsync(DeploymentStartupRe Task computerNameTask = ResolveComputerNameAsync(request.FallbackComputerName); Task hardwareTask = LoadHardwareAsync(); - Task targetDisksTask = LoadTargetDisksAsync(); + Task targetDisksTask = LoadTargetDisksAsync(request.RuntimeContext.Mode); Task catalogTask = _deploymentCatalogLoadService.LoadAsync(); await Task.WhenAll(computerNameTask, hardwareTask, targetDisksTask, catalogTask).ConfigureAwait(false); @@ -112,12 +115,38 @@ private async Task LoadHardwareAsync() } } - private async Task LoadTargetDisksAsync() + private async Task LoadTargetDisksAsync(DeploymentMode mode) { try { IReadOnlyList disks = await _targetDiskService.GetDisksAsync().ConfigureAwait(false); - return new TargetDiskLoadResult(disks); + if (mode != DeploymentMode.Recovery) + { + return new TargetDiskLoadResult(disks); + } + + int? recoveryDiskNumber = await _recoveryTargetDiskResolver.ResolveAsync().ConfigureAwait(false); + if (!recoveryDiskNumber.HasValue) + { + _logger.LogWarning("OS Recovery mode could not resolve the active recovery target disk."); + return new TargetDiskLoadResult([]); + } + + TargetDiskInfo? recoveryDisk = disks.FirstOrDefault(disk => disk.DiskNumber == recoveryDiskNumber.Value); + if (recoveryDisk is null) + { + _logger.LogWarning("OS Recovery target disk {DiskNumber} was not returned by disk discovery.", recoveryDiskNumber.Value); + return new TargetDiskLoadResult([]); + } + + return new TargetDiskLoadResult( + [ + recoveryDisk with + { + IsSelectable = true, + SelectionWarning = string.Empty + } + ]); } catch (Exception ex) { diff --git a/src/Foundry.Deploy/Services/Wizard/DeploymentWizardContext.cs b/src/Foundry.Deploy/Services/Wizard/DeploymentWizardContext.cs index f5695330..d1c4bd9e 100644 --- a/src/Foundry.Deploy/Services/Wizard/DeploymentWizardContext.cs +++ b/src/Foundry.Deploy/Services/Wizard/DeploymentWizardContext.cs @@ -31,6 +31,7 @@ public DeploymentWizardContext( public OperatingSystemCatalogViewModel OperatingSystemCatalog { get; } public DriverPackSelectionViewModel DriverPackSelection { get; } public string? DefaultTimeZoneId { get; private set; } + public DeployOsRecoverySettings OsRecovery { get; private set; } = new(); public CoreDeployNetworkSettings Network { get; private set; } = new(); public DeployOobeSettings Oobe { get; private set; } = new(); public DeployAppxRemovalSettings AppxRemoval { get; private set; } = new(); @@ -110,6 +111,7 @@ private void ApplyDeployConfiguration( string.IsNullOrWhiteSpace(Preparation.TargetComputerName) ? seedComputerName : Preparation.TargetComputerName); + OsRecovery = document.OsRecovery ?? new DeployOsRecoverySettings(); Network = document.Network ?? new CoreDeployNetworkSettings(); Oobe = document.Customization.Oobe ?? new DeployOobeSettings(); AppxRemoval = document.Customization.AppxRemoval ?? new DeployAppxRemovalSettings(); diff --git a/src/Foundry.Deploy/Strings/ar-SA/Resources.resx b/src/Foundry.Deploy/Strings/ar-SA/Resources.resx index 09e20442..da1303aa 100644 --- a/src/Foundry.Deploy/Strings/ar-SA/Resources.resx +++ b/src/Foundry.Deploy/Strings/ar-SA/Resources.resx @@ -509,4 +509,25 @@ تم تجهيز مساعد تسجيل Autopilot التفاعلي (محاكاة). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + استرداد Foundry + إعادة نشر Windows diff --git a/src/Foundry.Deploy/Strings/bg-BG/Resources.resx b/src/Foundry.Deploy/Strings/bg-BG/Resources.resx index ed155386..07addd97 100644 --- a/src/Foundry.Deploy/Strings/bg-BG/Resources.resx +++ b/src/Foundry.Deploy/Strings/bg-BG/Resources.resx @@ -509,4 +509,25 @@ Интерактивният помощник за регистрация на Autopilot е подготвен (симулация). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Възстановяване Foundry + Преинсталиране Windows diff --git a/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx b/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx index c3d75e16..43d7500a 100644 --- a/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx +++ b/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx @@ -509,4 +509,25 @@ Pokračovat v nasazování? Interaktivní asistent registrace Autopilotu byl připraven (simulace). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Obnova Foundry + Znovu nasadit Windows diff --git a/src/Foundry.Deploy/Strings/da-DK/Resources.resx b/src/Foundry.Deploy/Strings/da-DK/Resources.resx index 327f25ad..0e932a85 100644 --- a/src/Foundry.Deploy/Strings/da-DK/Resources.resx +++ b/src/Foundry.Deploy/Strings/da-DK/Resources.resx @@ -509,4 +509,25 @@ Vil du fortsætte med implementeringen? Den interaktive Autopilot-registreringsassistent er klargjort (simulation). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry-gendannelse + Genudrul Windows diff --git a/src/Foundry.Deploy/Strings/de-DE/Resources.resx b/src/Foundry.Deploy/Strings/de-DE/Resources.resx index c5b6ccfc..6e2b8532 100644 --- a/src/Foundry.Deploy/Strings/de-DE/Resources.resx +++ b/src/Foundry.Deploy/Strings/de-DE/Resources.resx @@ -509,4 +509,25 @@ Mit der Bereitstellung fortfahren? Der interaktive Autopilot-Registrierungsassistent wurde bereitgestellt (Simulation). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry-Wiederherstellung + Windows neu bereitstellen diff --git a/src/Foundry.Deploy/Strings/el-GR/Resources.resx b/src/Foundry.Deploy/Strings/el-GR/Resources.resx index 84071fb0..479c98ac 100644 --- a/src/Foundry.Deploy/Strings/el-GR/Resources.resx +++ b/src/Foundry.Deploy/Strings/el-GR/Resources.resx @@ -509,4 +509,25 @@ Ο διαδραστικός βοηθός εγγραφής Autopilot προετοιμάστηκε (προσομοίωση). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Ανάκτηση Foundry + Επανεγκατάσταση Windows diff --git a/src/Foundry.Deploy/Strings/en-GB/Resources.resx b/src/Foundry.Deploy/Strings/en-GB/Resources.resx index 8c2b199f..5ffb5ffa 100644 --- a/src/Foundry.Deploy/Strings/en-GB/Resources.resx +++ b/src/Foundry.Deploy/Strings/en-GB/Resources.resx @@ -509,4 +509,25 @@ Continue with deployment? Interactive Autopilot registration assistant staged (simulation). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry Recovery + Redeploy Windows diff --git a/src/Foundry.Deploy/Strings/en-US/Resources.resx b/src/Foundry.Deploy/Strings/en-US/Resources.resx index 37dd655f..71750f08 100644 --- a/src/Foundry.Deploy/Strings/en-US/Resources.resx +++ b/src/Foundry.Deploy/Strings/en-US/Resources.resx @@ -509,4 +509,25 @@ Continue with deployment? Interactive Autopilot registration assistant staged (simulation). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry Recovery + Redeploy Windows diff --git a/src/Foundry.Deploy/Strings/es-ES/Resources.resx b/src/Foundry.Deploy/Strings/es-ES/Resources.resx index e9722e1a..09e1d804 100644 --- a/src/Foundry.Deploy/Strings/es-ES/Resources.resx +++ b/src/Foundry.Deploy/Strings/es-ES/Resources.resx @@ -509,4 +509,25 @@ Sistema operativo: {4} Asistente de registro interactivo de Autopilot preparado (simulación). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Recuperación Foundry + Reinstalar Windows diff --git a/src/Foundry.Deploy/Strings/es-MX/Resources.resx b/src/Foundry.Deploy/Strings/es-MX/Resources.resx index 471f2d37..bbaebfd2 100644 --- a/src/Foundry.Deploy/Strings/es-MX/Resources.resx +++ b/src/Foundry.Deploy/Strings/es-MX/Resources.resx @@ -509,4 +509,25 @@ Sistema operativo: {4} Asistente de registro interactivo de Autopilot preparado (simulación). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Recuperación Foundry + Reinstalar Windows diff --git a/src/Foundry.Deploy/Strings/et-EE/Resources.resx b/src/Foundry.Deploy/Strings/et-EE/Resources.resx index 04dfbecf..2543ff26 100644 --- a/src/Foundry.Deploy/Strings/et-EE/Resources.resx +++ b/src/Foundry.Deploy/Strings/et-EE/Resources.resx @@ -509,4 +509,25 @@ Kas jätkata juurutamisega? Interaktiivne Autopiloti registreerimisabiline on ette valmistatud (simulatsioon). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry taaste + Juuruta Windows uuesti diff --git a/src/Foundry.Deploy/Strings/fi-FI/Resources.resx b/src/Foundry.Deploy/Strings/fi-FI/Resources.resx index 38510cff..95f39ecb 100644 --- a/src/Foundry.Deploy/Strings/fi-FI/Resources.resx +++ b/src/Foundry.Deploy/Strings/fi-FI/Resources.resx @@ -509,4 +509,25 @@ Jatketaanko käyttöönottoa? Vuorovaikutteinen Autopilot-rekisteröintiavustaja on valmisteltu (simulointi). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry-palautus + Asenna Windows uudelleen diff --git a/src/Foundry.Deploy/Strings/fr-CA/Resources.resx b/src/Foundry.Deploy/Strings/fr-CA/Resources.resx index e8a4ed38..9adaab28 100644 --- a/src/Foundry.Deploy/Strings/fr-CA/Resources.resx +++ b/src/Foundry.Deploy/Strings/fr-CA/Resources.resx @@ -509,4 +509,25 @@ Continuer le déploiement? Assistant d'enregistrement Autopilot interactif préparé (simulation). + Confirmer OS Recovery + OS Recovery conservera les partitions EFI, MSR et Recovery, puis remplacera la partition Windows du disque sélectionné. + +Disque : {0} +Modèle : {1} +Bus : {2} +Taille : {3} +Système d’exploitation : {4} + +Continuer avec OS Recovery ? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Récupération Foundry + Redéployer Windows diff --git a/src/Foundry.Deploy/Strings/fr-FR/Resources.resx b/src/Foundry.Deploy/Strings/fr-FR/Resources.resx index 4a93b4a9..34ce6c22 100644 --- a/src/Foundry.Deploy/Strings/fr-FR/Resources.resx +++ b/src/Foundry.Deploy/Strings/fr-FR/Resources.resx @@ -509,4 +509,25 @@ Continuer le déploiement ? Assistant d'enregistrement Autopilot interactif préparé (simulation). + Confirmer OS Recovery + OS Recovery conservera les partitions EFI, MSR et Recovery, puis remplacera la partition Windows du disque sélectionné. + +Disque : {0} +Modèle : {1} +Bus : {2} +Taille : {3} +Système d’exploitation : {4} + +Continuer avec OS Recovery ? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Récupération Foundry + Redéployer Windows diff --git a/src/Foundry.Deploy/Strings/he-IL/Resources.resx b/src/Foundry.Deploy/Strings/he-IL/Resources.resx index cb65f64a..cfdc5a06 100644 --- a/src/Foundry.Deploy/Strings/he-IL/Resources.resx +++ b/src/Foundry.Deploy/Strings/he-IL/Resources.resx @@ -509,4 +509,25 @@ ErrorCode=0x80070005 מסייע הרישום האינטראקטיבי של Autopilot הוכן (סימולציה). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + שחזור Foundry + פריסה מחדש של Windows diff --git a/src/Foundry.Deploy/Strings/hr-HR/Resources.resx b/src/Foundry.Deploy/Strings/hr-HR/Resources.resx index de02ecf2..38ed1d94 100644 --- a/src/Foundry.Deploy/Strings/hr-HR/Resources.resx +++ b/src/Foundry.Deploy/Strings/hr-HR/Resources.resx @@ -509,4 +509,25 @@ Nastaviti s implementacijom? Interaktivni pomoćnik za registraciju Autopilota je pripremljen (simulacija). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry oporavak + Ponovno uvedi Windows diff --git a/src/Foundry.Deploy/Strings/hu-HU/Resources.resx b/src/Foundry.Deploy/Strings/hu-HU/Resources.resx index 22a6da16..251a42e3 100644 --- a/src/Foundry.Deploy/Strings/hu-HU/Resources.resx +++ b/src/Foundry.Deploy/Strings/hu-HU/Resources.resx @@ -509,4 +509,25 @@ Folytatja a telepítést? Az interaktív Autopilot regisztrációs segéd előkészítve (szimuláció). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry-helyreállítás + Windows újratelepítése diff --git a/src/Foundry.Deploy/Strings/it-IT/Resources.resx b/src/Foundry.Deploy/Strings/it-IT/Resources.resx index 9d53f58d..30e40eff 100644 --- a/src/Foundry.Deploy/Strings/it-IT/Resources.resx +++ b/src/Foundry.Deploy/Strings/it-IT/Resources.resx @@ -509,4 +509,25 @@ Continuare con la distribuzione? Assistente di registrazione interattiva di Autopilot preparato (simulazione). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Ripristino Foundry + Reinstalla Windows diff --git a/src/Foundry.Deploy/Strings/ja-JP/Resources.resx b/src/Foundry.Deploy/Strings/ja-JP/Resources.resx index 3f334a4f..082ccd0f 100644 --- a/src/Foundry.Deploy/Strings/ja-JP/Resources.resx +++ b/src/Foundry.Deploy/Strings/ja-JP/Resources.resx @@ -509,4 +509,25 @@ 対話型 Autopilot 登録アシスタントをステージングしました (シミュレーション)。 + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry 回復 + Windows を再展開 diff --git a/src/Foundry.Deploy/Strings/ko-KR/Resources.resx b/src/Foundry.Deploy/Strings/ko-KR/Resources.resx index b1d24ab4..a716c3cc 100644 --- a/src/Foundry.Deploy/Strings/ko-KR/Resources.resx +++ b/src/Foundry.Deploy/Strings/ko-KR/Resources.resx @@ -509,4 +509,25 @@ 대화형 Autopilot 등록 도우미가 준비되었습니다(시뮬레이션). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry 복구 + Windows 재배포 diff --git a/src/Foundry.Deploy/Strings/lt-LT/Resources.resx b/src/Foundry.Deploy/Strings/lt-LT/Resources.resx index b7cfbbc7..aa0270de 100644 --- a/src/Foundry.Deploy/Strings/lt-LT/Resources.resx +++ b/src/Foundry.Deploy/Strings/lt-LT/Resources.resx @@ -509,4 +509,25 @@ Tęsti diegimą? Interaktyvus Autopilot registracijos pagalbininkas paruoštas (modeliavimas). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry atkūrimas + Iš naujo diegti Windows diff --git a/src/Foundry.Deploy/Strings/lv-LV/Resources.resx b/src/Foundry.Deploy/Strings/lv-LV/Resources.resx index 91da3fe9..e8c1774c 100644 --- a/src/Foundry.Deploy/Strings/lv-LV/Resources.resx +++ b/src/Foundry.Deploy/Strings/lv-LV/Resources.resx @@ -509,4 +509,25 @@ Vai turpināt izvietošanu? Interaktīvais Autopilot reģistrācijas palīgs ir sagatavots (simulācija). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry atkopšana + Pārizvietot Windows diff --git a/src/Foundry.Deploy/Strings/nb-NO/Resources.resx b/src/Foundry.Deploy/Strings/nb-NO/Resources.resx index f5094bd1..e3e6899c 100644 --- a/src/Foundry.Deploy/Strings/nb-NO/Resources.resx +++ b/src/Foundry.Deploy/Strings/nb-NO/Resources.resx @@ -509,4 +509,25 @@ Vil du fortsette med distribusjonen? Den interaktive Autopilot-registreringsassistenten er klargjort (simulering). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry-gjenoppretting + Rull ut Windows på nytt diff --git a/src/Foundry.Deploy/Strings/nl-NL/Resources.resx b/src/Foundry.Deploy/Strings/nl-NL/Resources.resx index 71cd0e66..4cb83201 100644 --- a/src/Foundry.Deploy/Strings/nl-NL/Resources.resx +++ b/src/Foundry.Deploy/Strings/nl-NL/Resources.resx @@ -509,4 +509,25 @@ Doorgaan met de implementatie? Interactieve Autopilot-registratieassistent voorbereid (simulatie). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry-herstel + Windows opnieuw uitrollen diff --git a/src/Foundry.Deploy/Strings/pl-PL/Resources.resx b/src/Foundry.Deploy/Strings/pl-PL/Resources.resx index ff1a14d7..6be67492 100644 --- a/src/Foundry.Deploy/Strings/pl-PL/Resources.resx +++ b/src/Foundry.Deploy/Strings/pl-PL/Resources.resx @@ -509,4 +509,25 @@ Kontynuować wdrażanie? Interaktywny asystent rejestracji Autopilot został przygotowany (symulacja). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Odzyskiwanie Foundry + Wdróż Windows ponownie diff --git a/src/Foundry.Deploy/Strings/pt-BR/Resources.resx b/src/Foundry.Deploy/Strings/pt-BR/Resources.resx index 1f9ff2fb..f18795ba 100644 --- a/src/Foundry.Deploy/Strings/pt-BR/Resources.resx +++ b/src/Foundry.Deploy/Strings/pt-BR/Resources.resx @@ -509,4 +509,25 @@ Continuar com a implantação? Assistente de registro interativo do Autopilot preparado (simulação). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Recuperação Foundry + Reimplantar Windows diff --git a/src/Foundry.Deploy/Strings/pt-PT/Resources.resx b/src/Foundry.Deploy/Strings/pt-PT/Resources.resx index 437ef9a4..ca552abd 100644 --- a/src/Foundry.Deploy/Strings/pt-PT/Resources.resx +++ b/src/Foundry.Deploy/Strings/pt-PT/Resources.resx @@ -509,4 +509,25 @@ Continuar com a implantação? Assistente de registo interativo do Autopilot preparado (simulação). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Recuperação Foundry + Reimplementar Windows diff --git a/src/Foundry.Deploy/Strings/ro-RO/Resources.resx b/src/Foundry.Deploy/Strings/ro-RO/Resources.resx index 8e259b4d..5f5f40ed 100644 --- a/src/Foundry.Deploy/Strings/ro-RO/Resources.resx +++ b/src/Foundry.Deploy/Strings/ro-RO/Resources.resx @@ -509,4 +509,25 @@ Continuați cu implementarea? Asistentul de înregistrare interactivă Autopilot a fost pregătit (simulare). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Recuperare Foundry + Redeplasați Windows diff --git a/src/Foundry.Deploy/Strings/ru-RU/Resources.resx b/src/Foundry.Deploy/Strings/ru-RU/Resources.resx index a4b66ad5..7a7f0b78 100644 --- a/src/Foundry.Deploy/Strings/ru-RU/Resources.resx +++ b/src/Foundry.Deploy/Strings/ru-RU/Resources.resx @@ -509,4 +509,25 @@ Интерактивный помощник регистрации Autopilot подготовлен (симуляция). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Восстановление Foundry + Развернуть Windows заново diff --git a/src/Foundry.Deploy/Strings/sk-SK/Resources.resx b/src/Foundry.Deploy/Strings/sk-SK/Resources.resx index d9231902..d548ab6c 100644 --- a/src/Foundry.Deploy/Strings/sk-SK/Resources.resx +++ b/src/Foundry.Deploy/Strings/sk-SK/Resources.resx @@ -509,4 +509,25 @@ Pokračovať v nasadzovaní? Interaktívny asistent registrácie Autopilotu bol pripravený (simulácia). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Obnova Foundry + Znovu nasadiť Windows diff --git a/src/Foundry.Deploy/Strings/sl-SI/Resources.resx b/src/Foundry.Deploy/Strings/sl-SI/Resources.resx index 064ff8d4..6e637eec 100644 --- a/src/Foundry.Deploy/Strings/sl-SI/Resources.resx +++ b/src/Foundry.Deploy/Strings/sl-SI/Resources.resx @@ -509,4 +509,25 @@ Ali želite nadaljevati z uvajanjem? Interaktivni pomočnik za registracijo Autopilot je pripravljen (simulacija). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Obnovitev Foundry + Znova uvedi Windows diff --git a/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx b/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx index 1825bcbc..dededd5c 100644 --- a/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx +++ b/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx @@ -509,4 +509,25 @@ Operativni sistem: {4} Interaktivni pomoćnik za registraciju Autopilota je pripremljen (simulacija). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry oporavak + Ponovo uvedi Windows diff --git a/src/Foundry.Deploy/Strings/sv-SE/Resources.resx b/src/Foundry.Deploy/Strings/sv-SE/Resources.resx index 4c63e91b..43c810ab 100644 --- a/src/Foundry.Deploy/Strings/sv-SE/Resources.resx +++ b/src/Foundry.Deploy/Strings/sv-SE/Resources.resx @@ -509,4 +509,25 @@ Fortsätt med implementeringen? Den interaktiva Autopilot-registreringsassistenten har förberetts (simulering). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry-återställning + Distribuera Windows igen diff --git a/src/Foundry.Deploy/Strings/th-TH/Resources.resx b/src/Foundry.Deploy/Strings/th-TH/Resources.resx index b175ae96..2011fd7d 100644 --- a/src/Foundry.Deploy/Strings/th-TH/Resources.resx +++ b/src/Foundry.Deploy/Strings/th-TH/Resources.resx @@ -509,4 +509,25 @@ จัดเตรียมผู้ช่วยลงทะเบียน Autopilot แบบโต้ตอบแล้ว (การจำลอง) + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + กู้คืน Foundry + ปรับใช้ Windows ใหม่ diff --git a/src/Foundry.Deploy/Strings/tr-TR/Resources.resx b/src/Foundry.Deploy/Strings/tr-TR/Resources.resx index 2eca99a9..752e1537 100644 --- a/src/Foundry.Deploy/Strings/tr-TR/Resources.resx +++ b/src/Foundry.Deploy/Strings/tr-TR/Resources.resx @@ -509,4 +509,25 @@ Dağıtıma devam edilsin mi? Etkileşimli Autopilot kayıt yardımcısı hazırlandı (simülasyon). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry Kurtarma + Windows'u yeniden dağıt diff --git a/src/Foundry.Deploy/Strings/uk-UA/Resources.resx b/src/Foundry.Deploy/Strings/uk-UA/Resources.resx index 4d78ec0b..07c1c628 100644 --- a/src/Foundry.Deploy/Strings/uk-UA/Resources.resx +++ b/src/Foundry.Deploy/Strings/uk-UA/Resources.resx @@ -509,4 +509,25 @@ ErrorCode=0x80070005 Інтерактивний помічник реєстрації Autopilot підготовлено (симуляція). + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Відновлення Foundry + Розгорнути Windows знову diff --git a/src/Foundry.Deploy/Strings/zh-CN/Resources.resx b/src/Foundry.Deploy/Strings/zh-CN/Resources.resx index bbff8202..23e33ae1 100644 --- a/src/Foundry.Deploy/Strings/zh-CN/Resources.resx +++ b/src/Foundry.Deploy/Strings/zh-CN/Resources.resx @@ -509,4 +509,25 @@ 交互式 Autopilot 注册助手已暂存(模拟)。 + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry 恢复 + 重新部署 Windows diff --git a/src/Foundry.Deploy/Strings/zh-TW/Resources.resx b/src/Foundry.Deploy/Strings/zh-TW/Resources.resx index f572b8fe..af6dccb5 100644 --- a/src/Foundry.Deploy/Strings/zh-TW/Resources.resx +++ b/src/Foundry.Deploy/Strings/zh-TW/Resources.resx @@ -509,4 +509,25 @@ 互動式 Autopilot 註冊助理已暫存 (模擬)。 + Confirm OS Recovery + OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + +Disk: {0} +Model: {1} +Bus: {2} +Size: {3} +Operating system: {4} + +Continue with OS Recovery? + Provision OS Recovery + Provisioning OS Recovery... + Injecting OS Recovery payload... + Committing WinRE changes... + Registering OS Recovery boot menu... + OS Recovery is disabled. + OS Recovery provisioning is skipped in recovery mode. + OS Recovery provisioned. + OS Recovery provisioned (simulation). + Foundry 復原 + 重新部署 Windows diff --git a/src/Foundry.Deploy/ViewModels/MainWindowViewModel.cs b/src/Foundry.Deploy/ViewModels/MainWindowViewModel.cs index 166ee38d..ae162a3f 100644 --- a/src/Foundry.Deploy/ViewModels/MainWindowViewModel.cs +++ b/src/Foundry.Deploy/ViewModels/MainWindowViewModel.cs @@ -275,6 +275,7 @@ private async Task StartDeploymentAsync() AutopilotProvisioningMode = Preparation.AutopilotProvisioningMode, SelectedAutopilotProfile = Preparation.SelectedAutopilotProfile, AutopilotHardwareHashUpload = Preparation.CreateAutopilotHardwareHashUploadForLaunch(), + OsRecovery = _wizardContext.OsRecovery, Network = _wizardContext.Network, Oobe = _wizardContext.Oobe, AppxRemoval = _wizardContext.AppxRemoval, diff --git a/src/Foundry.Telemetry.Tests/TelemetryBootMediaTargetResolverTests.cs b/src/Foundry.Telemetry.Tests/TelemetryBootMediaTargetResolverTests.cs index faf227dd..ba420253 100644 --- a/src/Foundry.Telemetry.Tests/TelemetryBootMediaTargetResolverTests.cs +++ b/src/Foundry.Telemetry.Tests/TelemetryBootMediaTargetResolverTests.cs @@ -9,8 +9,10 @@ public sealed class TelemetryBootMediaTargetResolverTests [InlineData(TelemetryRuntimeModes.Desktop, "Usb", TelemetryBootMediaTargets.None)] [InlineData(TelemetryRuntimeModes.WinPe, "Usb", TelemetryBootMediaTargets.Usb)] [InlineData(TelemetryRuntimeModes.WinPe, "Iso", TelemetryBootMediaTargets.Iso)] + [InlineData(TelemetryRuntimeModes.WinPe, "Recovery", TelemetryBootMediaTargets.Recovery)] [InlineData(TelemetryRuntimeModes.WinPe, "usb", TelemetryBootMediaTargets.Usb)] [InlineData(TelemetryRuntimeModes.WinPe, "iso", TelemetryBootMediaTargets.Iso)] + [InlineData(TelemetryRuntimeModes.WinPe, "recovery", TelemetryBootMediaTargets.Recovery)] [InlineData(TelemetryRuntimeModes.WinPe, null, TelemetryBootMediaTargets.Unknown)] [InlineData(TelemetryRuntimeModes.WinPe, "", TelemetryBootMediaTargets.Unknown)] [InlineData(TelemetryRuntimeModes.WinPe, "Foundry Cache", TelemetryBootMediaTargets.Unknown)] diff --git a/src/Foundry.Telemetry/TelemetryBootMediaTargetResolver.cs b/src/Foundry.Telemetry/TelemetryBootMediaTargetResolver.cs index 470f66a3..b2114546 100644 --- a/src/Foundry.Telemetry/TelemetryBootMediaTargetResolver.cs +++ b/src/Foundry.Telemetry/TelemetryBootMediaTargetResolver.cs @@ -22,6 +22,7 @@ public static string Resolve(string runtime, string? deploymentMode) { "iso" => TelemetryBootMediaTargets.Iso, "usb" => TelemetryBootMediaTargets.Usb, + "recovery" => TelemetryBootMediaTargets.Recovery, _ => TelemetryBootMediaTargets.Unknown }; } diff --git a/src/Foundry.Telemetry/TelemetryBootMediaTargets.cs b/src/Foundry.Telemetry/TelemetryBootMediaTargets.cs index c6248d2b..3e60fa05 100644 --- a/src/Foundry.Telemetry/TelemetryBootMediaTargets.cs +++ b/src/Foundry.Telemetry/TelemetryBootMediaTargets.cs @@ -7,6 +7,7 @@ public static class TelemetryBootMediaTargets { public const string Iso = "iso"; public const string Usb = "usb"; + public const string Recovery = "recovery"; public const string None = "none"; public const string Unknown = "unknown"; } diff --git a/src/Foundry/Assets/NavViewMenu/AppData.json b/src/Foundry/Assets/NavViewMenu/AppData.json index 3bcf6120..5b2ca328 100644 --- a/src/Foundry/Assets/NavViewMenu/AppData.json +++ b/src/Foundry/Assets/NavViewMenu/AppData.json @@ -102,6 +102,16 @@ "HideItem": true, "IsNew": false, "IsUpdated": false + }, + { + "UniqueId": "Foundry.Views.OsRecoveryPage", + "Title": "OS Recovery", + "LocalizeId": "Nav_OsRecoveryKey", + "UsexUid": true, + "IconGlyph": "E7BA", + "HideItem": true, + "IsNew": false, + "IsUpdated": false } ] }, diff --git a/src/Foundry/Assets/OsRecovery/winre-os-recovery-flow.png b/src/Foundry/Assets/OsRecovery/winre-os-recovery-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..0ae9ac9b05645ae625a9ff7005ed73d5db696233 GIT binary patch literal 27381 zcmeFZc~nzp*9RI)9V$g^9k2+bbw&lG%m|5A5t*x~%tRU)!)TbvV6}(^Au98fSRf!W zMCLgWC`3gP8BCZXMu-qXfB+#0N$#QAclf^Ves`_=$6f2*`~KnDBEdcD&{^-$021CMWgS7>|VW#XY<4z@q| zDiM1<>(qtBv|q1(d+LYW{mmgxtXU);Ih!{>`*aow3yJ;#9pXeVT`8mwtf1yOdeB9_ zoF(Tg0jz=c4LB4*H~s*1|FLP?22~gUeF>0Tzxso|0O<4Q`k+k!y?$Nw7C=C!PAQ$) zp!>46e7`}zwpG}5`-*GLATX!jxO$qE!uF~1%1_94of7A zlv9(y#3hU-c7$}DG~i-{J?X;rToy`F`tjGeGS(L-)g8Ou$223yQtNWJO*RqCVpD9G z!4>Q#wwj^G{%vn_v0!25MqE74(FYNTZ=79XZ)q8(K20tnCQNml6aM5FW)P3;D~Kh} zt2LPV)O46=i8|;M=`6*HdhCS3gQTZI%dULR!i{{v3*XD`ET6w(k5RH|O+S9irMj7G z(R8c2Uw60aj6#NsO>ybwZ*DxLtrXD{Ai)F34|H3Fh2J}TE-n7r@C@DA8L~#U zPW4AtD9h@$_=THQuqzLbu<|<)E+(>2NlfaE-YwU|cbq^fl9Y-H;wqw90UOz++)7~~ zyI||Y`w-hM5>wiI#}f9)#P2P_$(`MSLHNGK-TcfZ zFj+dAc}FmCV8mCyAr+5MDE;VpV&e2->^?LNkkq+&)7q7=fn%6^cJmrpBmM)ypeGmXWqn_Jocx z)m(C1I_=(XthoQyWQ6M_j!7Wdu+$G)!EgJ9RhZg^yxP3?=-UfEUX(hK%4$0HuFkxv zcGd}X$CnWrix3qO-)gdzaFrA`Dbp?w=^ecw_`O~M1d2YO z^!%t|u4(%@zxwf+v&PZUG>uz#f|e3`7zs14BKywGxrf@mHQAeMyQsDVpH&xb>ceyh zoc0H)+1~U!M7eLoeaJA^?HN$|p4a$; zARY^6vLVr1lvDYnt;+3IcMeK=s~0AJ+;Z zFXIMh4Ig$JfqLX|hXFsHpX%8sTWvP%6CKNIC|GVDGrWuBD)HYXA)eWxI_&9sDEa%m_nq@StH7?O@Ce0wB$%VXr>0QK6#-13p z8D}8M+};2@E}t>MF7#>~;V5^tpA)QXsuoR}VYjt?=j+u{t{Ju$8qI-sQ{Nmi%h*a) z!V$GwAV<5pEv%>_)88wDUaC{WBj_e(r5?F{uRGLgkf%d_vS1I=T#mLq?7T)qP#9u2)B`CUQ(`)RNF^@!@* zoudcU z=veAcsl+2@w282SxW_kxGbb|r&3#KJkVDT1A5nX7dPDMXWy{`@3@LN^n+bwbyRX)o ztp$5R1GZpdJ)b$H>vNoQY>XL$O+Vu7J=Vq2o@?W`tQx7zoi?`Yi$SfcZk6D{QA^qD z?@31qeXhGC4+UF+!UIxzZg*ZeEi>WI$L#~+Q#l3~6O}XD_qBJ1OCtF>VDdo1yS+lg zQ?3(CO@OtHxTuVVm_<` zSkLT?Lg>L^g{i4O^y1EZof9oG2d0$Yhzx)23Um6F zS8~(yz+_W1qB0&C@om23a9@>qc+mT}CsavHGSwf-5w=oR%-bW0Tl%_waik1fRyajK zo@~}3A?^6d?+rrIQlggLZi2a?H{}C6ZB+RHd1wU zPtHx@edf&_Ez@AuIr9`2sN6twTen)UJ8;0iV$1dDARWTwoFHG_vIHn1DZ@A2wML6v zS-Vi(G|pj>P_E7InNJXe9hO)_od9O`1CeRH_E?%e_k$jGr}^(17X-o#`F20jP&nQEX&FIxta43gQwfKZEnFpLs%GWIX&H?P~4#BfmU6 z{A(6?oZz%rw;k#<$uSApnSY+D(W^BQ?1Iv%#qDXOEHGV1sVDEv|M7n1>*+nOe=x;y z-PzaWB~8V08Ecumj4{sdT=wmP1HDExMCo}i;7?GPjLL3dxk-X*-%0=BOodzJ3(jmQ zb#(Q9%k5<^$U~!@w^`epvwcI`F#Ho;Nc4%|U&h{E_k$$xm`BX!KSrzk#@36${?dy(YC4hHFDi8}d5T9mk3^X=`y+{AB{4UI*gyJWz8&}F@J`rezcNcC zkV2vHLewOOMP8fwNV+}E_u#9opnc$?7;!1VDy8tIMvjHhb!J^3>;w_kyfgD*B4&pA z5zFxetfW+w_ee_=r%0{<4j_n}>`??3&nba~pn)M>_Uhp+uTq^`rlt zV?QIr<;axxaX+?c*ksTgdBy-cBb`dmr<$C+ImpU4?yVO&m-8(nHX1Q{$)-kP{N1zS zGcjdLr(FGqT0e6z^xXN6P0nVFW%Is9Ee+H@Wn9apx`#LUizk!acJcJg=|-v~yGhTd z$K5k41?u6e}FB zrcda)Q7e95ZR)OT+QxtQ=vCDfY|B&c`~7(biMn6Bjj>pH=lx*gs5U7w>2QQ0uVjt^FqqzfM%{P~v-_&V+nY{m`j<)8Cq( z*D=VpX45&l0L7!CDNdq4wpZ+StYH^pU* zE;s$<3S|YzflwX&q;4I&U~9^W5&C${%f=H1xnZp5K-aC7sz5O$}X8!JXfDtT# zu3%xtd|`hS@Iei~<@@KpDpF~S4yM@4#*@LRLayBlW+qf7A1YL}P--CS+0zCeF z=l{zZ?HlG$F*5`zio$SsM=NC?dHbQ;>&sl=_v_UBwFBo^ZD@9n03kmO+=7#NJX7bc z<#8lYWRug=A+nfN(?Bi~vtttoq6i?^wM_X(Trl~JP$V1We+ei5W}xbWxL=iVus8m^ z>~U

#oCK)|~}CG+Chj_V+$q3|Q25RnSB_i>XYBfh@&jv@Jv(2i-~sAl=#J>&*Fh zN_l19DYs9ojEPhETM79FP^d^@S{MAA<)Vldb>-*<=rsC+@LDg2yhYSxIxTv^Y5LeB z7xQ2~Y!C>g4WRy==0kOEWxrP4`3E=N-hAxJLIjR8O|n0NS#((9Nv4#usHmM9U0IyC z8RRqx6!HV5A>cwIe?F5?2FK%2cso=#=$3=M!U6z?k4|l66&Q3o_dJ}gzU!63p|dc$ zlCjt2OAj)k*EIxZqjxLh^SvOxg)jIOIhk$zYY#s!GTaQ7Wi-+cfEJqkLBU5tT-5|g zD+-M01&>cMBKA#1_it?p!9YcU6=CjxprHijx$i!ra7!lpFJMBvdk<(YAtPSXy&&|D zITx_|T4*8n6hN&#kGlM?W{9VH{ z{tA&$C`9C|!?~zxgjt&rrJGM+u5*ZJZ=^N9$B$(sc}0EAa%?&}AWgqPkcQ7)sU>ww zy#+8+R{K7sSm;6D;jsXYA=F5;lHvjN?UHukv9^j#o&AU-0pycI!P0FhD=8xUScFnF z-6<4bgOpqhQtEc_W*cC3)CetDHh@1L9WY6!IrKDrH6{3(J^#r^u;Y z5JRo0ZRz=U{O-T&LH1Pnz*Q&Ip2Qwx^gtuT$x9hsHoG_;nY?jNiNfIq+MSwX?WuS$IAUA3dq8H^yd=zqnQ?@vfLW4cwB|68V<_VYq z1kzG#52U*X+9PTz3reNe9SgcQhIA-~=iML(hs>2Km(e%^-_Tre+6L+?%o`n!2pY#a zC_IaJU1pG?*#yg}^a#fp9K*Fui@!ll^b4*mE*QLkAlj?Enf~!3s_v;vB5k(-P&dnF zX+=kFR>C|?k@xfqr#a@P3{?Jn3wc=g2GcN-ZyFnkbQi_+;!4SObQvy&x9OH8&}tM< z+4R}2dY`x%^Wa{+9eLdnCJrVW`@n86<1RLt&;p@0b`4YL!Tt9J!u0dx194?YD^4or z_k2F-#|!*AAOu76_E5ro;UQJHc-Ac?ja%%n{)F<6&D#2@nHu7d=_LpzQXY?VBg-@S zFDG;%DU+tucjhZ`;gLbr%znY1-lkBDbpd)AbD==DKj!>ska;j9}@m zjOj#`=L)xRvT0MEeYzQDUQ&DJ?QrlUMs+^ElIy&$P>9@@{4fSe4otS?KMXHlUU?c_ zY2adhfBHdiW*hlgC6Tj!!%N)`7dL}>MMF67zFL(JEf&`|u7aK)1fbo(!uJN5n6pn? zeHVMK`L~EvsLM8NYh}$olt8DkPA9uTauN4~Ds+5Wsu;`>DRaJUJ$gE1MKpl~_@Cd$ zK6x7d&TGnRYHQ8Hrt))aLjf(r$hS4LpVyeX~xZwOE0qAuxAvzchr~4w_IFs zfn_NkN#l!V)Yy}e$G>)z}?g_He}!|sF#+jc{+um{d0vh*0Z zrK~0~XRQ+P?Af(EZ|1v{5!FD)AhmdEb!gii^o`No4Q=Md%gEg+mj=V^226ER@u_}0 zW()^R>^NXgW_;Cq#Lhrw7?jA*2RriW`VEp{jis^ zO@&&44l8fFmN(FU?v=VSxsG`o9<(UiT)#f?Ui6ga{m7FkMkPs(TDVd^#wCw#&2WRhqCUtaWs4+14_gQ>QjK>Z1oUKyrdy5=$>)Hq{3YdLkTyK=U_0d;1Y- z|25H4)FbrparE!KR4vG%x8y4fPei`sYV4_Q7gNr#@$fzYx`>ZJn$w4MkMu*6Ni?Ea z$w&x&8J7E5)N&yR+}n03*6uXTcjav3YOE_pdlc+Vnc?fpE6b)=g`?q02&!{j#UZRi z(!|!O*)XR%{=pWXw{(d**QtkVH)_t!DrnN5JF)-hRb!`dd==(PxS@f%Dy^p|QYnCB zS1pL6n_tZh`CGaGWGr+wTofQSYij$2^@GFKArN6>Ahs~;pGVNnXF zMp>>*4N&xNTi*Hf>zq?mZtR8CFX?o2g(<>RJDL4Dd0IfhZfhhBN50ivf}=n5z{QXC zW+WZCFec7qO2DHSBJPLvg_}iL3)^NPOUO6uRn!#o1cV@h2zdQf_S77*j#Q1vw}R36 zJ+iF};ozu=R3}l~2sBFM{$4sIxz~^s-fGn5z#%*=M()4=Fqmm-9|3r*CgnmMH+O=N zBtSgWI2S=~TS8_V-8rz~xLGu8=7?)ciU_e-by5vEU=}>3M7RO=&kuM+*J~@D!LWoL z;KE3j4i5?;M3IlS`Bo#-Jn0!DspJV{t7Y@&;7rAMm7zqZXV$9I%^&O`U-2HjD)*W} zVP`U%0&@dti@H97kI~nACq|dQcXCogdTH~QxTf;U`N?Q%-Am6F`2Ct<_RK@*&!~%F zh{l8Q+_q`tSJP_w^f~t~;Z1S&S{Kx6K7ojMz&lMLh{4YVx=)2SI_q9dq-r?zHFH`H z<-%%lel4OfecBKE4~{z-+J`4+G`Nj9__xgJS|CeVI)~p%4ZoUCE}M$0_IO67Q%xr?=et@8d!` z&2!t1HgD;RY;Wz4^Ql_ZPjO50r2dOLUnqlN`gQErgYsP&0sNex?Aay~IlQ%$EpB9G5P9uuGWImqAL*1&Dyz( z=2;}fWZWHglZ$1IAd`L?>LkkQb55!&(dRJOz0;mqP6wcPvQPoU1*SZBEgHi{=9qPnHJml$^htQ3SQQ*FnrXbL?v39jZiH5Wj2_>)M zeiV+~`SmU7GhhRbQYJryMvD~RJ6xj#ZlS!~bN?YYikmqk ziW3OHrSerz{Y=zY?eNW|g*q4i`|mRsJ6aJmLCk9A+I4b|;Ql<0cmesohlQ1N-MHT$ zqves=zuKqkffmqfw*Cnugq8W1qreCZaVX2tkbj;E)Gm|a_d~;Kd}qp$bi~*I2-U4S zC3!NgBSJj=)y(OupiuCzi~cP0X2fa}KKT`7(qu$m}PDP(4wmU^cV1 zg%WsG_FL2PX3+%*otrsDG)ebV*0`HQ^;A7LUa8M4Tc2!Q_HHsJ6gPc9K0#NZ=SIr) zFGRZc2qtVW<)Vn>^Gs7yGHTtwW&MxCt(Nv3g73yptO#w{CCFJUh~IQnz-~P*+iL}p zC5Tt7a6EXh$Sjf5a<-OGGvZqm9vsIY%|`QM_uopriktCiS&yahGe6eo`{Tt72X z&8w=h7nM{iuEC7iL?Em};vyMQ0O8(%zPz1PltdZajRvzTH@nz2EFB9#)CzMV$ zt%5@RfHBJBP20Hla?Z$RIh=PR3eW%e&q0F!9HRKo0TN*9_rD#UiT)l6Ou#k>qnwl;o257fXh!Z-rVAUQNb2d7ii%-?Jyk+^MFd4#V!SyIU69C?{K^|V5=wuC6h1zQ|fCy%kfl| z_=Exwj{R%}PTpb1kfq9;qCu*<2k}vLM3YZEL)r!1voY}z(TmNp#nDV(>_tDRI&_vw z)(0tnIJMch_kD*{>{52Ao?(ob@)Nimt)bkWvgtSns5ERytSk9NK0sN2%Q1V>+M)2~ z-gon@Wm@K+_+9)8Z*+}*qP)|N`4&n7j%NLnS$(ZBQvN?QK!|w%Lr?`^;^<^xK}VSZ zw1u<)(*Nb|X?}o>15iNJiZQ6$oaDEH?c7jqM85RuWT!?_`M00Ub~EdXyyA#S%w+l7 zV{VANx|_A4@?FaQ2#ZY`)Jt3{Shh4(6YFxPQr<#&561o=mEB>i#IzfVv?C-^<;29u9I zx+g?bhqo2LZC~SbMqqgnxS0vbQ8SffeuV|#`ChY@9Ki(>A!ZdXv<>5Pgn53+0qzNc zrm?tSjYdcX-_1B9_dMr@|9bA2izD9{TZUFKnMz?p{xpAZ{-2`GtUqI&)UWA6-E4WH zviIGU#iUie#0W31yeqVGkhhUB2Zwx_Vd<-Jv_hu;D+Ts>3!SR9__M(q zPIhmF%}$zIx0wo5x(DI7>}6C8#I9S$C)}M*`28oevn#R&p4(Oj=T$H(*V-2^G)6Lo zE*Mr?ypTzYbJBS7RpxRY_ZcRNkuhaT;py(-6Nr+AYI(*JFx%Kx`VlqpLh#WyMBDUb>yCRFC6B`#*ADws^ErZE^zDchw zA(+F>ka<)qs@OGi&gnkzNa4eHZ~D}EU5<80 zi^MjHQIAHxSbK;osB7KE+|CnsHJfpS?kdCAztPf~=*0$H77q|;h*QLs##hu@MuaOO zFM~-m*uG$#at#^)yWr-@chP&wb@#VxD@H?Ij&pAgWX^QgHH75HJ!6FD;bvoCwh~MR zV;vupMi*+XNNxQ~1!d|3Z9<2s3HKA91e*^ARjv%XiL!s06gaJ~N2<~L#SP2ixQtaN z=tNhoCv$d)`?g+;=|7pD8*`jfYE053O3h-&wl@8n_3~kdpxa?H?ZLgeOl`)@+S%hs z9mqa?d8w7xCcJ#c*);NS1+~tMzwTVPTiC!b<_waEwE#}1R?S*^sptBxp4o}{f#;%k z9ENhxgx**w?*&#UCwYNg5WKcxQS^4^6JLrVr!5CBJ())zLAX4X1eIwaJZeT-5u{$n zyqkC>Rgr zL=T%;vxd?y8_&m^^g8CV3WaC>C4)~-bm_RB(f0SaU#Us+n)<~fTVT-DltyimwVsPL z8{VhHsnjRv6M2kgdqlDTFaGR@m!)fKt(F9qW(_o1nNd<{0H56Sq#o~1S*R~REGT}} z>zapib9Ed@tLK`^T&hj`u6rVyS6jj-PRxwZHPaE!rU_oh*|m&}e%Y#V@kr!w+Z$6- z_otNe1a&09g?P+GVx4NDV~+;ZcRJ(tKr5O*n-VUB1G! zW?5*M&*$o9RKmP5y>R^t|IDwYC%0X4-TQk+Q1Iov&iXEK-h1~~?DDEx6y@E}TN#Ny z{t$ob(R7Tb?c`Xw0WEHpd3mG|$f?FF>{Uf{>O15_ozF=#P`dmENZnT)Ev4cv9wn_U3nNhk%U)6Z)q5$@=B zNdqOemqsd=1*a6bp%P~zT)q~gk8w=oNX)_VLl@V@nz@X6mu8leIYY)jfYT86?n|OB z{2BNC@8T*QrRNGJwwvr7Jzg)>p44XsX~=e^_y3aalm83aK1_QmtQi-j+kS3UfqHeM zGHxOsS;H{qm%?oPabK9FbPnQv>h!W|#za+or>K0Ur6R}CI) zjf?++OHQ8j@$cw5*xLs8NumQ*N?US2&JNcIi3_F-mUK~!35U9PrW_A5BSbRNUg~Nc z2Vg=qB4!ZU;_pM4x*@@ZsWR^SqrYN2N8H9ze8d%ZoNDAkpFn1jHh$Kg&$zC_#K zbY4@7)0ReyVa%7kH~ISV{*tw<+3#Zht^r#zo>2`VNSRl4wM8kY1dg6@w*s#9xfcc=(2 zy$I5qt-iMGiPN`Z)~#>7%?Zy-JtZVlxahh#gf%PbAz4Z_`FG^v@7f6MZ+=s|o~vAP z65RxgmHM33KKV+J@MfenNx5u>3+xlIHcN(F=U2oJpwfnSA- zyfw2w7G-1LRUsaCbz?O8^ip@v!SM`Uu%qmAxIF=vghN7J*NA;d3$aYSt`1Ih>v$|T zGzJ`R3&+=^yztr{n!|{f zJX($SSp{8PJ76kLt@yZtoZfsem+d{d-oe61W?rdCa<|?S7{|2d(+{h%(`Kva9G7E* z4cOVGhl0j%w6S{qtHTcdO)7#Wr%7p88!WbLAVOC04Br1@r+YLZXi-{8WjX_(dP$|tap z*IX0IB->J%FW)x>*ij?$VTE(i8WllG!i&z1`zM&;As(p&_DAQ+n1%m}=S=s`JBs(_ zj9S#)@Sl9C0h*=>D&;R_y?Lq7OFdum3DwbaHgH%G)yqBgfX2P_1eV*X;&S;mNZPxWZcTV@N-z(>BAx|++gdqFYCf$?9lY3 zNF{_%IGJB=;K+dRr}Wie-uMy#(9r##XqCCw;NjM=JYwzm`<1wlIc*!A>HG@Z+S@Lu zhfQd6d`cip_$DrQxn$Ir(cmM(4%60J+%Y4GlI#rev?Yq3RJMAQht(H}maX;Cb(2En%3yM2=(PxT^in?5DFULMByfpK0irfyRsa z1Mh!pe#?X9RGM6Y2;fEvE3>6_KKMO!$>n)7vf~o6@f4qUaxephjW3V24SO{@F8mbH ztCe8p6~SEive8l~5DKN>rCQrw0^@G}{mi2* zg-^;jziID7twIo=3zhR_Z%47qJ#xV)RH_1{d2% z;U57p`?of}G{;uhNFUHum7b$FDv|WTTRjQ5_?~R5#fM!t3ggav!VsTls_1l$cSru) z6IZ{Ll13nt*p;h0^3>-yed>z~KuIq$F5ME=ErX)^#mwe^i@mc@;pW6vc4F}HG0O6#;&&YiEn4_pGMr@_A~yNt6b__7$`F`IBN@H^mj0BsHU zFKRJY4n5ij<6efIztID<9{~MuweVlWFR7d6WBJ%PNgPF7!b$k5eg!O7k)Mn+<}QL| z@1bakgM3szWyY9M#a9wHhGZMvDnLman%Wp8Z+Uz#FnhoiDSl6Y>|4mOXI_@>e5eJq z^%elFDA?4vfm8ZGyB}(?Hw7P(caE~eBv})%LN3r({SMGk3x&{)!B4@aV+Y*26~>&` zw*d0&S%IJ2kZaGszlO~U9ibP>?+#!WqU6*D`3K56hf}q_-~O)|H{hEGmYxDe-WR zZ#P&mdoHb`2puNKODs7z@cxE4z!?6sg8tnB-ikq)vWSX%|I00ak2s0BuHXD`CSL|M zEHmp4{4WAHErXa#|NAXKuvBtL2#Z1BwC0b0#m6@>Z$CAsJ2yK6{`N&HX+UAj9P4qS z;C~eV#0L^a&a~^7LQ%oeG}~I_Gw)B%$6#;P*{;A?&+-ev-Zbv#X;SmC9*m!|s%M9&7KE6)AxiN%vgH8xCS(4bQVI%1#%P@4R zL*{tE>g9^`VgNS|HDJD8H^136?H<gDY*6wFh9>iP~t*EYe0*jAP&&i&r$=bG7}4FFc0{P3eg1oytS;xO_YE6RVFlx59xfAS!+Z zKBKGyxyc&QKIlV@JNs#E0Ez8ejQbG4>N&xk3Xpt!d5; z)_YgR!)2Fe2#O{pOOIo%Tu&bzZXOSvGx8sYG;ekIYJw1Skh!7C8qBi3XGh(^XqF^$ zQFRYFGAiHMnabA?9-P$8#D~SMJ`8qqsS(t|YLK_<407C}g91nuv2*RZ=A~N_#Z^W? zjvKB6!Bjelo6G8-SHR2aG9q!_NA(ay_D<^S+!}dSEf0I+Ydg91^T%;C#d~bsybdH1YO{# zz`VNCv*!m)A>b&;1@AHA^p&L)^m(p_vqGYOn-3a++)%`L*mwtjH<6QF!gx;qdyF5l z3BRW%d{e6xaoe76_ofdQ-@Xp1O7s)uMaWKFG9$AE{$MnsOO zez4mZQ&TjeMr&9d380b}%mitwYfYci)vDHWNA0AMLvCXyES@|F)h$0cBAjEV&7JkN z_J(x{-5;T=p6td2j%rrGc5A<+S}6v}p8dF|vi#+~UL&!~lB=xbc06+T`V1mM@@{R3 z$HGO4cQuI2LK=GMca}Fs*O-;UbqizrXXqfi?Ex3bKBz-+6^4<|CiWnG)vaJQ@|H9r z?|p=R1=OGS1ee=(&18Bxr?te_PMawT)UfJ@^Tr{Rqp{Okj-PG(l39vBkGQm4Gm99` zc|c*Tw?*S=VLh6p%r?oB-nP||phv!4`*|zc9=V+eq}AF;B2~kjZq~2`Gyi#xiB=4K z1UO~V&%|0X3*Os}A$%64(oLN^qaL(}ES$!*pv*PwwY42rml(8Z~n|o60Jp(fk z5#Eadet1QeUe>k2Rmk&vESAj@gm4qE)5uz0K9x%t^0shx1!mcRQL5InDRvton@&xy zypz`a$+hyD3v4SZY7C;IG8hAzE4mI=%)SNX92yc&N?r-G;b=prJ7l+AuiBT=n4IWW zyeeTZ7@X`1o!3tM06+$$v9;SS2ixr07H3)|-3xZ0fYn*>bIsI`oz5!7qD+ zdrq$U_X~^V<~Ct+ElJZ$-fE8p=|{|!*5lL%A+YZxdB9mw%AF#KL*mEQ86jpa6X|-q zJah4g*B=?DfO+R?891LCG378^LtjnvYb$$|>pVF$9P4BimWLPVm*Y74ftkHWv5h7q zqLK1xb7brw#L7O!Z1Z z7`Y@o+0DkkZEoDsSLs;sl$w5E>TB~>!_41En^BTv+)lSz$r-167l!1^f7Nd4yb0%!&!_6SWmBO#S={sM}S((ihNy^z&SY<(imaPnah>`11bK zZv5k#DFb>0Mo?x$5>3>raM@WUgW6`bn8iX$&KSa|ZkL}5dXTN=s}sEal`XFQy8#me z^>RJ#Mp}RDHz;yV-HhQRqOMNcO(%nyhiLAuAfndMl8;$&23pL(7}!jmBB}TAB%(|s zF4VSevBqTO-x8R1NyVmpC-X9k6xU7OTGZ9zViT1b-;*@9fW6 z{qELK`HKy&X2p(YQl)diHeVG&>L*TdpEwfRy)D-wbBvK{6M*oj_Y5yr@?bMA5BoA9 zzyOViGKW;RlEQ-~R+=qX@BlVJa8YS}?DMXSWY0S=1ucvtTA0C34Q!T}6MqR6#Wdg2 zKKXPJGOI~0sV9}6dA&=`(YyS;tS~I&NAnl?vJ#%6BzOB%raiM{a9>gi>E_pct9_Ye zntZAnp>P@*`-&Kf;f})SDN}8W!u*DiUaa1;M@tb8^gnqyKb!IUh;MCUhCAc_a*Z61 z>OGdSi->GHQT!v#T;MY18_}|!*UGs}(&2i`#Aswi&}nJ=>!@7Wt!cxqx@O^!)41$l zm+0ljChBShxZPLZ#y_`1@50*tHzl^C(Ef|Z*H2pW8`F0)3u)-^x3_+=W*)S9CdYjV zHxo6#O=(*-+Z6S@!T<0N1mGGe>P7CL)@&-ic~d{ik1poM=Z6X_E3jg zLlkLO0v|0Nhwl-B_cV3ArhDl7uwjndw_oS&>)6c8nf*f4Yt3=F z$lSk53lsC;*e9{U*$iGdaBkJNE`l>Rt>@vY_lliAy)YE(bTJ5s@ZC)6h9ySZ!I&GP z>9m(|mCWz6dg4r{UCpO$e3;tiG~(baieD8KMDU2J`>-*l1mI>Z+HxK+Kc#`zfGvL* zZrwV4h`XlF&baKZ(<1D<1k~#mpOQa0rOynZ(q;>`6W%v~?GW>L3ugKs*?b}Bhc;`< zZrDv}>Z2R&xpGvj)=5{TqyT>~argdEB}tCXySM0T-N)xCg+J3AF>B)EPzQ$4wS5&w0QMo&IFw1hkRRd|Ks+y~@#&l)o3WP-;E>KP=d%lRhOUdWpUUqJFV|<=hSS zlV5S4_sai2aNx^PY?4iRxK;eWM%=++X!v$BmX9ODaHm2v?hWIAj zgcpYchNk~cV0+*7+_~}rL6NU!&P?C5JkD8D!OZY%SduXD-Ic-@FHdenUj;bMLSbQN z%>Vf!9d`y{QrT!GP3bAnWy)9g%-Bz=^-Kz+Avn=@KmX;(Lnj!6T6(jl$AlYu|D1C0 zO^3l>Z|3E${==n0r_RnU9X*)4es|WsZkU_vWm;2GG8}ZcmVK^yaFdVJ2vz*avzVWY#f}a!Cb`t z^;RGO{I#tNz_F>b0b<1SKEwbeH}tC8+HI_Yj##*vgyl81j!Q@`SShasYap34$dner z8(yze1D*FN0+q=khkFj%NDv;&CpgBvD2db$}-a{ud_7 zune9u1MD#{t`qtUX0qU>C8%JMI$$CR1J~hu*p+Jv!5UdLm}Bet?v6HGcVNe1(l|9b zXx7|loQr%gjrL5ftN}9*sv#hBzX```Y$~gn8%#BT9B!r|@cUnW-a3oadQO}kTz+tMEr#s3mA$OJFd3N{Mp6tGv%oK9 z-!L{hAYVm{v;J*qn@aY2(nb7rEYX(J=8v6fGRRMQud7l|IWiQ&Y(

QNO-eMN1z& zl%E_mH05-m_f8v!UUK@&`R_T#z4Mc;?}3gI6WV^7cjX-CS~hC9GZNno^$*hQ1mdym zP(sB@Q~eIcw0erfkLL%aFSTwIsXELcqw&4zx8=PR2$!(ej{S9-Qa(-}O@SQE0)A@J zBk5tb|1_UvDUqqpKHr7Y91FRkoXB~Xb{*2U`tNeq-0exeK6(1^2~;|>U#{)#-?BV; zSjrcE*j#BCygJgRhcv%5(?e4fR3bk4vGHWiut?Mcp$M*E%Cvg&x$dj3K{6tI$G5>M$sXBJAq8k3 zs*|m^`q$HK`oZ<&t#J3$aTW6jCp<8;+`x6ORGr)``#&=l9%L<-n2Utmk;Y*}Iy{9j zgE!C_b>h`kMhMfUOn3G!_Q9Xh^!7nd(+&+ABzNO58LbRd@S&v7qoO*{)~@pP(LL=1 z{Og-7I~%kYfR-nF8tD;w=fWFFftk*aD`5i#mpm20J*=$VSx|+(ay_uuTf|xo%#Zgr;5#Nn8qW%#?)Vvnt`DTFda*qskwO~K-_?qWz1SO z@4fOU+1$Hpg`M`E%i|nzJt&B2xZzK*;Y8tInP&A?VeB1ZY9rk#gdzib!z7?&d2lTk zL>LFO`eOMvQ{;kJqbGoW)5n;0K%>FygtNbc8@^8eLi|d`YbKcD~kpnHPB`Za|JN zL9n9qL1n6>F#XTscyOu9xzR6UAEIAEh+IJ?hB0um4Pp~UIZ+8NDI&4Lf?b&}e#sFI zhsgrC*A4l1!v@&a@;H3p`Y6GnnmJYaDGwdPtj00^kM__@7A8V7<)zG;@-nRD0!u@Btbz# zAtXdZLEND4#%bo)Ip@RqoiFE{4}ID{E}OmAUh8?DwOG&kFUfDN(me2%bWiZC(L0zY z87bq2N+jk1EaP;4Xqo9?koo#~SzJ)*UOk{GY^YUZ5UV|NfHRN#FWV0FBQ^ zGB=WP*BBQ2H$B~1>Nq8vDQ-r%qv^N0u-EzU#>Vc1IoU9}zDQmX?5MI{8D(-*l9Jhm zXmb1V_}tjScNKcwIy}D&o~Gf#McO*UH-y5xH?n_4WZ=Lqnt8QGv7J0q%-LBHPV%3we4qY ztmFmZ!4N<*Ih=Ss6cp{hDlW^cG^XG~@X*}P#4PGug4LJoYZnWPss?CkBDL%4Oo~8< znisTRXN4~9v~_?xO7D#c!RUz-iu1b?J~`_CHIO?2hjP@Lvk0KMte?I3XUKnp2tnTD zk%b&lBRW1>KOg8i(x+@ha3d0#oeGv?(kCL?3r?!bVp8{2)$_>P*CE@+5t03W%jiLq z83V`mo??Fs)<+o+)o71}H>dnl9v7+S2TA)<572AyLsg?kCCU46>4ho`dsVFH5lVOI z@4*)cu`FYBT2$(PxOek(!LlP@?GUT|JWvU`2J#6~1y4!A#0q4Avn+~X&9C%ZL|zMy(vc=$)nA! z7GR7&$(ixMsJXiii=aW2u46Mq0_QkJAOs$i^WjURrBAzhTAmFF!${T?Ng zf646I_^F!|w2P~Hxg}u$RMAh!9s3mpyN6bR9&Oq*_K#~*53o|_(%L4^zcQarUtRQB zc=-$Xmy_XzL9?D+;4E1{wAyOhlbZ_eh1ItH{4s`JJ-~aR7O#ASnzh zV}@!jutLO1X5T;fVq@W6W9dryVfdv>dEkGydkioyFMLnXaL()g!H>Ss$J8$DV}|Rm z zx=L&>n!DcaZT{iPK;gRHW{h)!>6nv{2;8^aA{g%_>8+l|&oddQ08tB9_)Wnttz>b( z#qKt}-obWFo{hc7HLUCtH@)=frE+G((&vPwAV55S;eh{ZNY8n*@*lT~Tzib267<9a)(OCLfg!NYlOJu{Y=pUD+i zu3TLndT#wYLy;1BmF(23RbUaxVb40WuIs~HBtM!ynuHv?MU@J$LQcG*&y2Akky3_9 zuHyoQTfLI;_CjB^3NfE2p>i6A$l?*9ck(i=NUTgB;nrsYF4N2s!4633DTH66^Si|B z!kuVG!AABsajWvaC>Q1^Doo@tR^(e0{h={)Ys zQ{%1`i$fG)SEP_IWh%n#EF@5gpeAH^(*|Rxld&PAl@gu9dv`MrjE_zzTL;=ma)43G zr%b%e@4VtHX*6qVZ{oiLt^$iC?jA`N6qsrc389J6(~Hp)6%;~CVn?-;HK(-TPCRNw zItuP&-ARtoKY8`j$}mwBWUV$1EbY4852*zkj-H@(LO{Q6(YF&IRw5U@>6A)3b z_6U}xn+#SYULUoEl~rm~Z#^I#$_;>Z^QD#;Ca+OTrK?N7Cm)8HEl{B!Cx|Tm02zuV zjQybTFEJ-{o00~luP&hM;*ZZaf4rbl@t7B#xFmN)FVLitv+nq>x1CDA#n<#=nos(A zZe@?5*Y-Le45lp79v69Ye6!!zlPw)yac*lv;iN&_Fn05+8V8mp+4S_OuwK=}B&RnT zN*#_ZwOMD2_du4t(WnMIsGUlOr`Z?eH^>1;1ZA3FHLk(!`&OkQbDR+Jm61)(v|IkX zWNKD9jvn;oq zwG!EBj}TbBSTAi-070KY7-GMv&!A(b2b|6Py0vbbd2SRfm$^A#z1UGR6tk#e3U9&7*!Mqa7EJQfPypYL^JMrX?{)ow>X~IQ3=> zc@oQ~?7)PHCMLVo+)e$wh-j+=Vz1>lxT$D6DK6#9nqV<~iXZu9Ci8MYbdgJky+{27 z3(Z&3yX2w0Xor53&HC*IrKd08gHpmJcWZD6)~O0dfLQ`K%JL6IoI*9oJ25Zz$?6sB z|FXuF@CR=q%&Eg^mHGmQ=&HTJ)wRES{DD@7Lfk25wO`BJLivcp?w{UV(g(W2`o<_m zB4K>*m3WDWvqx%PjKiwM&FR^E4EkcRkB;xiv3IWz7^M#zguj2-UIiy|u+6tY1v{7U z+^5r@XMRu%pV4|0(YITCI6ue`%gVgZ09=mm-^RyHt6V@g;|v&ddyeVmjUJWb)>fuX z#niWlqnD=(ogb4-F+T(UP$chv?AF6>C9CY}s4^1ieSLifsMg^W8ot9L`lJKrK7K}W zn|Udqs{HGHsx zQBMGTy^yl3I{bAN=QX)xV@`N%-h-71SrF2+s1?^bRVl_#F_X4*rfzfnhB&OaS~5$S z+2Nmrps?fCql+gfVLUbSwaGQcsfmh;5$1Vu$Z*&QQAQ)PIN6vFDGpbQVI zNC<9r8PHa z^f*9Z_J)-VC{S04OYKRsvcc`=#{8-&tlNmy?8F?%#!NHL>1!DaxlHFo;?|iF#{5}( zu$jT3eJ<=an95YMkBFQI6-o26@eo%XHUcV@I>ef2gtlu`FgoBdr5)%3D6@LAc#)4Z zB+91BB|4WJPfsnCp5tkbRrFx9JbBUisrr)-#?)t45`-YA=NWIj+*jQ?nSViF5fZYe zO&iOB>8gxF=Os9F-hW&7%V0xd!nwMM6+5k^UR%?27sdkkdeB7INQVxZ+OEoX>zj#P( z`m4~(?^vVdFoGP!cAwq|@^`rJT8z=g^S!1k!g_}prPJxgjwN@EFN1W9E5>F#%x;r^ z7P=dN@CKHxL?kx86%0ao2K{Em3ohT?V(i~@9V82bhtKVMz$f{+b(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/Foundry/Services/Configuration/FoundryConfigurationStateService.cs b/src/Foundry/Services/Configuration/FoundryConfigurationStateService.cs index b35cdb0a..ae94d12f 100644 --- a/src/Foundry/Services/Configuration/FoundryConfigurationStateService.cs +++ b/src/Foundry/Services/Configuration/FoundryConfigurationStateService.cs @@ -126,6 +126,15 @@ public void UpdateLocalization(LocalizationSettings settings) StateChanged?.Invoke(this, EventArgs.Empty); } + /// + public void UpdateOsRecovery(OsRecoverySettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + Current = Current with { OsRecovery = settings }; + Save(); + StateChanged?.Invoke(this, EventArgs.Empty); + } + /// public void UpdateOperatingSystemSelection(OperatingSystemSelectionSettings settings) { diff --git a/src/Foundry/Services/Configuration/IFoundryConfigurationStateService.cs b/src/Foundry/Services/Configuration/IFoundryConfigurationStateService.cs index 861cebcf..2538e6b4 100644 --- a/src/Foundry/Services/Configuration/IFoundryConfigurationStateService.cs +++ b/src/Foundry/Services/Configuration/IFoundryConfigurationStateService.cs @@ -97,6 +97,12 @@ public interface IFoundryConfigurationStateService /// New localization settings. void UpdateLocalization(LocalizationSettings settings); + ///

+ /// Replaces the OS recovery configuration section. + /// + /// New OS recovery settings. + void UpdateOsRecovery(OsRecoverySettings settings); + /// /// Replaces the customization configuration section. /// diff --git a/src/Foundry/Strings/ar-SA/Resources.resw b/src/Foundry/Strings/ar-SA/Resources.resw index 93b71751..a3a20c2f 100644 --- a/src/Foundry/Strings/ar-SA/Resources.resw +++ b/src/Foundry/Strings/ar-SA/Resources.resw @@ -2121,4 +2121,61 @@ راجع الجاهزية واختر إخراج ISO أو USB ثم ابدأ إنشاء الوسائط. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/bg-BG/Resources.resw b/src/Foundry/Strings/bg-BG/Resources.resw index bffc5dfa..fb1290d3 100644 --- a/src/Foundry/Strings/bg-BG/Resources.resw +++ b/src/Foundry/Strings/bg-BG/Resources.resw @@ -2121,4 +2121,61 @@ Прегледайте готовността, изберете ISO или USB изход и стартирайте създаването на носител. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/cs-CZ/Resources.resw b/src/Foundry/Strings/cs-CZ/Resources.resw index 170553ae..90083d28 100644 --- a/src/Foundry/Strings/cs-CZ/Resources.resw +++ b/src/Foundry/Strings/cs-CZ/Resources.resw @@ -2121,4 +2121,61 @@ Zkontrolujte připravenost, vyberte výstup ISO nebo USB a spusťte vytvoření média. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/da-DK/Resources.resw b/src/Foundry/Strings/da-DK/Resources.resw index 97df87a9..bfd820bd 100644 --- a/src/Foundry/Strings/da-DK/Resources.resw +++ b/src/Foundry/Strings/da-DK/Resources.resw @@ -2121,4 +2121,61 @@ Gennemgå klarheden, vælg ISO- eller USB-output, og start medieoprettelsen. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/de-DE/Resources.resw b/src/Foundry/Strings/de-DE/Resources.resw index fa318ea6..07a71d39 100644 --- a/src/Foundry/Strings/de-DE/Resources.resw +++ b/src/Foundry/Strings/de-DE/Resources.resw @@ -2121,4 +2121,61 @@ Prüfen Sie die Bereitschaft, wählen Sie eine ISO- oder USB-Ausgabe aus, und starten Sie die Medienerstellung. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/el-GR/Resources.resw b/src/Foundry/Strings/el-GR/Resources.resw index 6705494c..be6a8cd7 100644 --- a/src/Foundry/Strings/el-GR/Resources.resw +++ b/src/Foundry/Strings/el-GR/Resources.resw @@ -2121,4 +2121,61 @@ Ελέγξτε την ετοιμότητα, επιλέξτε έξοδο ISO ή USB και ξεκινήστε τη δημιουργία μέσου. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/en-GB/Resources.resw b/src/Foundry/Strings/en-GB/Resources.resw index 164a54ac..c302af3b 100644 --- a/src/Foundry/Strings/en-GB/Resources.resw +++ b/src/Foundry/Strings/en-GB/Resources.resw @@ -2121,4 +2121,61 @@ Review readiness, choose ISO or USB output, and start media creation. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/en-US/Resources.resw b/src/Foundry/Strings/en-US/Resources.resw index 44858eb6..80923b02 100644 --- a/src/Foundry/Strings/en-US/Resources.resw +++ b/src/Foundry/Strings/en-US/Resources.resw @@ -2121,4 +2121,61 @@ Review readiness, choose ISO or USB output, and start media creation. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/es-ES/Resources.resw b/src/Foundry/Strings/es-ES/Resources.resw index d967dec6..10578a13 100644 --- a/src/Foundry/Strings/es-ES/Resources.resw +++ b/src/Foundry/Strings/es-ES/Resources.resw @@ -2121,4 +2121,61 @@ Revise la preparación, elija una salida ISO o USB e inicie la creación del medio. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/es-MX/Resources.resw b/src/Foundry/Strings/es-MX/Resources.resw index 74ca2682..cd9e9733 100644 --- a/src/Foundry/Strings/es-MX/Resources.resw +++ b/src/Foundry/Strings/es-MX/Resources.resw @@ -2121,4 +2121,61 @@ Revise la preparación, elija una salida ISO o USB e inicie la creación del medio. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/et-EE/Resources.resw b/src/Foundry/Strings/et-EE/Resources.resw index a6fd5417..8898c0f0 100644 --- a/src/Foundry/Strings/et-EE/Resources.resw +++ b/src/Foundry/Strings/et-EE/Resources.resw @@ -2121,4 +2121,61 @@ Kontrollige valmisolekut, valige ISO või USB väljund ja alustage meediumi loomist. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/fi-FI/Resources.resw b/src/Foundry/Strings/fi-FI/Resources.resw index 986f7c23..4c85bce1 100644 --- a/src/Foundry/Strings/fi-FI/Resources.resw +++ b/src/Foundry/Strings/fi-FI/Resources.resw @@ -2121,4 +2121,61 @@ Tarkista valmius, valitse ISO- tai USB-tuloste ja käynnistä median luonti. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/fr-CA/Resources.resw b/src/Foundry/Strings/fr-CA/Resources.resw index ed20a236..75557438 100644 --- a/src/Foundry/Strings/fr-CA/Resources.resw +++ b/src/Foundry/Strings/fr-CA/Resources.resw @@ -2121,4 +2121,61 @@ Vérifiez l’état, choisissez une sortie ISO ou USB, puis lancez la création du support. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/fr-FR/Resources.resw b/src/Foundry/Strings/fr-FR/Resources.resw index 55985ad4..66ad06b0 100644 --- a/src/Foundry/Strings/fr-FR/Resources.resw +++ b/src/Foundry/Strings/fr-FR/Resources.resw @@ -2121,4 +2121,61 @@ Vérifiez la disponibilité, choisissez une sortie ISO ou USB, puis démarrez la création du média. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/he-IL/Resources.resw b/src/Foundry/Strings/he-IL/Resources.resw index b71148bf..2816a554 100644 --- a/src/Foundry/Strings/he-IL/Resources.resw +++ b/src/Foundry/Strings/he-IL/Resources.resw @@ -2121,4 +2121,61 @@ בדוק מוכנות, בחר פלט ISO או USB והתחל ביצירת המדיה. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/hr-HR/Resources.resw b/src/Foundry/Strings/hr-HR/Resources.resw index 16173f1c..3e93f273 100644 --- a/src/Foundry/Strings/hr-HR/Resources.resw +++ b/src/Foundry/Strings/hr-HR/Resources.resw @@ -2121,4 +2121,61 @@ Pregledajte spremnost, odaberite ISO ili USB izlaz i pokrenite stvaranje medija. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/hu-HU/Resources.resw b/src/Foundry/Strings/hu-HU/Resources.resw index f7242cc0..6a1112d8 100644 --- a/src/Foundry/Strings/hu-HU/Resources.resw +++ b/src/Foundry/Strings/hu-HU/Resources.resw @@ -2121,4 +2121,61 @@ Tekintse át a készenlétet, válasszon ISO vagy USB kimenetet, majd indítsa el az adathordozó létrehozását. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/it-IT/Resources.resw b/src/Foundry/Strings/it-IT/Resources.resw index ae11c654..67addca0 100644 --- a/src/Foundry/Strings/it-IT/Resources.resw +++ b/src/Foundry/Strings/it-IT/Resources.resw @@ -2121,4 +2121,61 @@ Verifica la preparazione, scegli un output ISO o USB e avvia la creazione del supporto. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/ja-JP/Resources.resw b/src/Foundry/Strings/ja-JP/Resources.resw index 07e07dca..c9b0a769 100644 --- a/src/Foundry/Strings/ja-JP/Resources.resw +++ b/src/Foundry/Strings/ja-JP/Resources.resw @@ -2121,4 +2121,61 @@ 準備状況を確認し、ISO または USB 出力を選択して、メディアの作成を開始します。 + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/ko-KR/Resources.resw b/src/Foundry/Strings/ko-KR/Resources.resw index aeaed98b..b76341b7 100644 --- a/src/Foundry/Strings/ko-KR/Resources.resw +++ b/src/Foundry/Strings/ko-KR/Resources.resw @@ -2121,4 +2121,61 @@ 준비 상태를 검토하고 ISO 또는 USB 출력을 선택한 다음 미디어 만들기를 시작합니다. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/lt-LT/Resources.resw b/src/Foundry/Strings/lt-LT/Resources.resw index c4bc7669..9b2c02b1 100644 --- a/src/Foundry/Strings/lt-LT/Resources.resw +++ b/src/Foundry/Strings/lt-LT/Resources.resw @@ -2121,4 +2121,61 @@ Peržiūrėkite parengtį, pasirinkite ISO arba USB išvestį ir pradėkite laikmenos kūrimą. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/lv-LV/Resources.resw b/src/Foundry/Strings/lv-LV/Resources.resw index 95c22aad..58c9323c 100644 --- a/src/Foundry/Strings/lv-LV/Resources.resw +++ b/src/Foundry/Strings/lv-LV/Resources.resw @@ -2121,4 +2121,61 @@ Pārskatiet gatavību, izvēlieties ISO vai USB izvadi un sāciet datu nesēja izveidi. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/nb-NO/Resources.resw b/src/Foundry/Strings/nb-NO/Resources.resw index ceaf3fe1..f565942a 100644 --- a/src/Foundry/Strings/nb-NO/Resources.resw +++ b/src/Foundry/Strings/nb-NO/Resources.resw @@ -2121,4 +2121,61 @@ Kontroller beredskapen, velg ISO- eller USB-utdata, og start medieopprettingen. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/nl-NL/Resources.resw b/src/Foundry/Strings/nl-NL/Resources.resw index f554f03a..85ce2696 100644 --- a/src/Foundry/Strings/nl-NL/Resources.resw +++ b/src/Foundry/Strings/nl-NL/Resources.resw @@ -2121,4 +2121,61 @@ Controleer de gereedheid, kies ISO- of USB-uitvoer en start het maken van media. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/pl-PL/Resources.resw b/src/Foundry/Strings/pl-PL/Resources.resw index 22ed23e2..5329d788 100644 --- a/src/Foundry/Strings/pl-PL/Resources.resw +++ b/src/Foundry/Strings/pl-PL/Resources.resw @@ -2121,4 +2121,61 @@ Sprawdź gotowość, wybierz wyjście ISO lub USB i rozpocznij tworzenie nośnika. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/pt-BR/Resources.resw b/src/Foundry/Strings/pt-BR/Resources.resw index f394cc34..b638f2c6 100644 --- a/src/Foundry/Strings/pt-BR/Resources.resw +++ b/src/Foundry/Strings/pt-BR/Resources.resw @@ -2121,4 +2121,61 @@ Revise a prontidão, escolha uma saída ISO ou USB e inicie a criação da mídia. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/pt-PT/Resources.resw b/src/Foundry/Strings/pt-PT/Resources.resw index 1202a058..d7f8ccec 100644 --- a/src/Foundry/Strings/pt-PT/Resources.resw +++ b/src/Foundry/Strings/pt-PT/Resources.resw @@ -2121,4 +2121,61 @@ Reveja a preparação, escolha uma saída ISO ou USB e inicie a criação do suporte. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/ro-RO/Resources.resw b/src/Foundry/Strings/ro-RO/Resources.resw index f81a8def..c7e33cfd 100644 --- a/src/Foundry/Strings/ro-RO/Resources.resw +++ b/src/Foundry/Strings/ro-RO/Resources.resw @@ -2121,4 +2121,61 @@ Revizuiți starea, alegeți ieșirea ISO sau USB și porniți crearea suportului. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/ru-RU/Resources.resw b/src/Foundry/Strings/ru-RU/Resources.resw index 0f39066b..a2372323 100644 --- a/src/Foundry/Strings/ru-RU/Resources.resw +++ b/src/Foundry/Strings/ru-RU/Resources.resw @@ -2121,4 +2121,61 @@ Проверьте готовность, выберите вывод ISO или USB и запустите создание носителя. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/sk-SK/Resources.resw b/src/Foundry/Strings/sk-SK/Resources.resw index df2129fc..76083cc6 100644 --- a/src/Foundry/Strings/sk-SK/Resources.resw +++ b/src/Foundry/Strings/sk-SK/Resources.resw @@ -2121,4 +2121,61 @@ Skontrolujte pripravenosť, vyberte výstup ISO alebo USB a spustite vytvorenie média. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/sl-SI/Resources.resw b/src/Foundry/Strings/sl-SI/Resources.resw index 2ace6c4f..257e92e2 100644 --- a/src/Foundry/Strings/sl-SI/Resources.resw +++ b/src/Foundry/Strings/sl-SI/Resources.resw @@ -2121,4 +2121,61 @@ Preverite pripravljenost, izberite izhod ISO ali USB in začnite ustvarjanje medija. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/sr-Latn-RS/Resources.resw b/src/Foundry/Strings/sr-Latn-RS/Resources.resw index 100e0ed2..f7ed53a7 100644 --- a/src/Foundry/Strings/sr-Latn-RS/Resources.resw +++ b/src/Foundry/Strings/sr-Latn-RS/Resources.resw @@ -2121,4 +2121,61 @@ Pregledajte spremnost, izaberite ISO ili USB izlaz i pokrenite kreiranje medija. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/sv-SE/Resources.resw b/src/Foundry/Strings/sv-SE/Resources.resw index d2897c88..e8bce930 100644 --- a/src/Foundry/Strings/sv-SE/Resources.resw +++ b/src/Foundry/Strings/sv-SE/Resources.resw @@ -2121,4 +2121,61 @@ Granska beredskapen, välj ISO- eller USB-utdata och starta medieskapandet. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/th-TH/Resources.resw b/src/Foundry/Strings/th-TH/Resources.resw index a20bf90f..39c4b6be 100644 --- a/src/Foundry/Strings/th-TH/Resources.resw +++ b/src/Foundry/Strings/th-TH/Resources.resw @@ -2121,4 +2121,61 @@ ตรวจสอบความพร้อม เลือกเอาต์พุต ISO หรือ USB แล้วเริ่มสร้างสื่อ + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/tr-TR/Resources.resw b/src/Foundry/Strings/tr-TR/Resources.resw index d1eda324..55545396 100644 --- a/src/Foundry/Strings/tr-TR/Resources.resw +++ b/src/Foundry/Strings/tr-TR/Resources.resw @@ -2121,4 +2121,61 @@ Hazırlığı gözden geçirin, ISO veya USB çıktısını seçin ve medya oluşturmayı başlatın. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/uk-UA/Resources.resw b/src/Foundry/Strings/uk-UA/Resources.resw index 48eacb57..fd70b0f0 100644 --- a/src/Foundry/Strings/uk-UA/Resources.resw +++ b/src/Foundry/Strings/uk-UA/Resources.resw @@ -2121,4 +2121,61 @@ Перевірте готовність, виберіть вихід ISO або USB і запустіть створення носія. + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/zh-CN/Resources.resw b/src/Foundry/Strings/zh-CN/Resources.resw index 0c616a4b..b158677b 100644 --- a/src/Foundry/Strings/zh-CN/Resources.resw +++ b/src/Foundry/Strings/zh-CN/Resources.resw @@ -2121,4 +2121,61 @@ 检查就绪情况,选择 ISO 或 USB 输出,然后开始创建媒体。 + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/Strings/zh-TW/Resources.resw b/src/Foundry/Strings/zh-TW/Resources.resw index ca9a6261..44a89071 100644 --- a/src/Foundry/Strings/zh-TW/Resources.resw +++ b/src/Foundry/Strings/zh-TW/Resources.resw @@ -2121,4 +2121,61 @@ 檢閱就緒狀態,選擇 ISO 或 USB 輸出,然後開始建立媒體。 + + Configure OS Recovery availability. + + + OS Recovery + + + OS Recovery + + + OS Recovery + + + Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + + + Enable OS Recovery + + + Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + + + Enable OS Recovery + + + Why it exists + + + OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + + + When to use it + + + Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + + + WinRE path + + + Troubleshoot > Advanced options > Foundry Recovery + + + Limitations + + + This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + + + Recovery flow + + + The placeholder image shows the intended WinRE navigation path and can be replaced later. + + + WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + diff --git a/src/Foundry/ViewModels/OsRecoveryConfigurationViewModel.cs b/src/Foundry/ViewModels/OsRecoveryConfigurationViewModel.cs new file mode 100644 index 00000000..ebe7e3ce --- /dev/null +++ b/src/Foundry/ViewModels/OsRecoveryConfigurationViewModel.cs @@ -0,0 +1,137 @@ +using Foundry.Core.Models.Configuration; +using Foundry.Services.Configuration; +using Foundry.Services.Localization; + +namespace Foundry.ViewModels; + +/// +/// Backs the OS Recovery page with the current authoring toggle and explanatory text. +/// +public sealed partial class OsRecoveryConfigurationViewModel : ObservableObject, IDisposable +{ + private readonly IApplicationLocalizationService localizationService; + private readonly IFoundryConfigurationStateService configurationStateService; + private bool isUpdatingFromState; + + public OsRecoveryConfigurationViewModel( + IApplicationLocalizationService localizationService, + IFoundryConfigurationStateService configurationStateService) + { + this.localizationService = localizationService; + this.configurationStateService = configurationStateService; + RefreshLocalizedText(); + RefreshConfigurationState(); + localizationService.LanguageChanged += OnLanguageChanged; + configurationStateService.StateChanged += OnConfigurationStateChanged; + } + + [ObservableProperty] + public partial string PageTitle { get; set; } = string.Empty; + + [ObservableProperty] + public partial string PageDescription { get; set; } = string.Empty; + + [ObservableProperty] + public partial string EnableHeader { get; set; } = string.Empty; + + [ObservableProperty] + public partial string EnableDescription { get; set; } = string.Empty; + + [ObservableProperty] + public partial string EnableToggleText { get; set; } = string.Empty; + + [ObservableProperty] + public partial string WhyHeader { get; set; } = string.Empty; + + [ObservableProperty] + public partial string WhyDescription { get; set; } = string.Empty; + + [ObservableProperty] + public partial string WhenHeader { get; set; } = string.Empty; + + [ObservableProperty] + public partial string WhenDescription { get; set; } = string.Empty; + + [ObservableProperty] + public partial string WinRePathHeader { get; set; } = string.Empty; + + [ObservableProperty] + public partial string WinRePathDescription { get; set; } = string.Empty; + + [ObservableProperty] + public partial string LimitationsHeader { get; set; } = string.Empty; + + [ObservableProperty] + public partial string LimitationsDescription { get; set; } = string.Empty; + + [ObservableProperty] + public partial string FlowHeader { get; set; } = string.Empty; + + [ObservableProperty] + public partial string FlowDescription { get; set; } = string.Empty; + + [ObservableProperty] + public partial string FlowImageAutomationName { get; set; } = string.Empty; + + [ObservableProperty] + public partial bool IsOsRecoveryEnabled { get; set; } + + public string DocumentationUrl => string.Empty; + + /// + public void Dispose() + { + localizationService.LanguageChanged -= OnLanguageChanged; + configurationStateService.StateChanged -= OnConfigurationStateChanged; + } + + private void OnLanguageChanged(object? sender, ApplicationLanguageChangedEventArgs e) + { + RefreshLocalizedText(); + } + + private void OnConfigurationStateChanged(object? sender, EventArgs e) + { + RefreshConfigurationState(); + } + + partial void OnIsOsRecoveryEnabledChanged(bool value) + { + if (isUpdatingFromState) + { + return; + } + + configurationStateService.UpdateOsRecovery(new OsRecoverySettings + { + IsEnabled = value + }); + } + + private void RefreshConfigurationState() + { + isUpdatingFromState = true; + IsOsRecoveryEnabled = configurationStateService.Current.OsRecovery.IsEnabled; + isUpdatingFromState = false; + } + + private void RefreshLocalizedText() + { + PageTitle = localizationService.GetString("OsRecoveryPage_Title.Text"); + PageDescription = localizationService.GetString("OsRecovery.PageDescription"); + EnableHeader = localizationService.GetString("OsRecovery.EnableHeader"); + EnableDescription = localizationService.GetString("OsRecovery.EnableDescription"); + EnableToggleText = localizationService.GetString("OsRecovery.EnableToggleText"); + WhyHeader = localizationService.GetString("OsRecovery.WhyHeader"); + WhyDescription = localizationService.GetString("OsRecovery.WhyDescription"); + WhenHeader = localizationService.GetString("OsRecovery.WhenHeader"); + WhenDescription = localizationService.GetString("OsRecovery.WhenDescription"); + WinRePathHeader = localizationService.GetString("OsRecovery.WinRePathHeader"); + WinRePathDescription = localizationService.GetString("OsRecovery.WinRePathDescription"); + LimitationsHeader = localizationService.GetString("OsRecovery.LimitationsHeader"); + LimitationsDescription = localizationService.GetString("OsRecovery.LimitationsDescription"); + FlowHeader = localizationService.GetString("OsRecovery.FlowHeader"); + FlowDescription = localizationService.GetString("OsRecovery.FlowDescription"); + FlowImageAutomationName = localizationService.GetString("OsRecovery.FlowImageAutomationName"); + } +} diff --git a/src/Foundry/Views/OsRecoveryPage.xaml b/src/Foundry/Views/OsRecoveryPage.xaml new file mode 100644 index 00000000..c7fa8882 --- /dev/null +++ b/src/Foundry/Views/OsRecoveryPage.xaml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Foundry/Views/OsRecoveryPage.xaml.cs b/src/Foundry/Views/OsRecoveryPage.xaml.cs new file mode 100644 index 00000000..c2cb6818 --- /dev/null +++ b/src/Foundry/Views/OsRecoveryPage.xaml.cs @@ -0,0 +1,19 @@ +namespace Foundry.Views; + +public sealed partial class OsRecoveryPage : Page +{ + public OsRecoveryConfigurationViewModel ViewModel { get; } + + public OsRecoveryPage() + { + ViewModel = App.GetService(); + InitializeComponent(); + Unloaded += OnUnloaded; + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + Unloaded -= OnUnloaded; + ViewModel.Dispose(); + } +} From ac4e1e875d81106aab77ed306f96daa79fd0bdee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Mon, 15 Jun 2026 19:36:47 +0200 Subject: [PATCH 03/11] chore(os-recovery): remove stale recovery audit remnants --- scripts/Test-FoundryOsRecoveryWinRe.ps1 | 20 ++----------------- src/Foundry/FoundryApplicationInfo.cs | 5 +++++ .../OsRecoveryConfigurationViewModel.cs | 2 +- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/scripts/Test-FoundryOsRecoveryWinRe.ps1 b/scripts/Test-FoundryOsRecoveryWinRe.ps1 index 79b2a682..a25e9bc1 100644 --- a/scripts/Test-FoundryOsRecoveryWinRe.ps1 +++ b/scripts/Test-FoundryOsRecoveryWinRe.ps1 @@ -19,12 +19,7 @@ param( [Parameter()] [string[]]$LauncherCandidates = @( 'Sources\Recovery\Tools\FoundryRecoveryLauncher.cmd', - 'FoundryRecoveryLauncher.cmd', - 'FoundryRecovery.exe', - 'FoundryRecoveryLauncher.exe', - 'Foundry.RecoveryLauncher.exe', - 'Foundry.Recovery.Tool.exe', - 'Foundry.WinRE.Launcher.exe' + 'FoundryRecoveryLauncher.cmd' ), [Parameter()] @@ -35,7 +30,6 @@ param( [Parameter()] [string[]]$ExcludedConfigPatterns = @( - 'Foundry\\Config\\foundry.deployment.config.json', 'Foundry\\Config\\foundry.connect.provisioning-source.txt', 'Foundry\\Config\\foundry.deploy.provisioning-source.txt', 'Foundry\\Config\\Secrets\\*', @@ -184,18 +178,8 @@ if ($SkipReAgentC) { } } -# Launcher artifact (name-based + regex fallback) +# Launcher artifact $launcher = Test-ExistsAny -Root $root -Candidates $LauncherCandidates -if (-not $launcher) { - $launcherCandidatesRegex = Get-ChildItem -Path $root -Recurse -File -ErrorAction SilentlyContinue | - Where-Object { $_.Name -match '(?i)foundry.*recovery.*\.(exe|bat|cmd|ps1)$' } | - Select-Object -First 1 - - if ($launcherCandidatesRegex) { - $launcher = $launcherCandidatesRegex.FullName - } -} - if ($launcher) { $checkResults.Add("PASS Launcher: $launcher") $successCount++ diff --git a/src/Foundry/FoundryApplicationInfo.cs b/src/Foundry/FoundryApplicationInfo.cs index 188a5607..e70dd5a6 100644 --- a/src/Foundry/FoundryApplicationInfo.cs +++ b/src/Foundry/FoundryApplicationInfo.cs @@ -39,6 +39,11 @@ public static class FoundryApplicationInfo /// public const string NetworkDocumentationUrl = "https://foundry-osd.github.io/docs/configure/network"; + /// + /// Gets the OS Recovery documentation URL. + /// + public const string OsRecoveryDocumentationUrl = "https://foundry-osd.github.io/docs/deploy/os-recovery"; + /// /// Gets the media creation documentation URL. /// diff --git a/src/Foundry/ViewModels/OsRecoveryConfigurationViewModel.cs b/src/Foundry/ViewModels/OsRecoveryConfigurationViewModel.cs index ebe7e3ce..f7815324 100644 --- a/src/Foundry/ViewModels/OsRecoveryConfigurationViewModel.cs +++ b/src/Foundry/ViewModels/OsRecoveryConfigurationViewModel.cs @@ -76,7 +76,7 @@ public OsRecoveryConfigurationViewModel( [ObservableProperty] public partial bool IsOsRecoveryEnabled { get; set; } - public string DocumentationUrl => string.Empty; + public string DocumentationUrl => FoundryApplicationInfo.OsRecoveryDocumentationUrl; /// public void Dispose() From 3a3706d3e2f9e05f7ed7dcd0a65955d1ab47b8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Mon, 15 Jun 2026 20:17:02 +0200 Subject: [PATCH 04/11] fix(os-recovery): localize recovery resources --- .../Strings/ar-SA/Resources.resx | 32 ++++----- .../Strings/bg-BG/Resources.resx | 32 ++++----- .../Strings/cs-CZ/Resources.resx | 28 ++++---- .../Strings/da-DK/Resources.resx | 26 ++++---- .../Strings/de-DE/Resources.resx | 30 ++++----- .../Strings/el-GR/Resources.resx | 32 ++++----- .../Strings/es-ES/Resources.resx | 32 ++++----- .../Strings/es-MX/Resources.resx | 32 ++++----- .../Strings/et-EE/Resources.resx | 32 ++++----- .../Strings/fi-FI/Resources.resx | 32 ++++----- .../Strings/fr-CA/Resources.resx | 22 +++---- .../Strings/fr-FR/Resources.resx | 22 +++---- .../Strings/he-IL/Resources.resx | 32 ++++----- .../Strings/hr-HR/Resources.resx | 28 ++++---- .../Strings/hu-HU/Resources.resx | 32 ++++----- .../Strings/it-IT/Resources.resx | 32 ++++----- .../Strings/ja-JP/Resources.resx | 32 ++++----- .../Strings/ko-KR/Resources.resx | 32 ++++----- .../Strings/lt-LT/Resources.resx | 32 ++++----- .../Strings/lv-LV/Resources.resx | 32 ++++----- .../Strings/nb-NO/Resources.resx | 30 ++++----- .../Strings/nl-NL/Resources.resx | 28 ++++---- .../Strings/pl-PL/Resources.resx | 32 ++++----- .../Strings/pt-BR/Resources.resx | 32 ++++----- .../Strings/pt-PT/Resources.resx | 32 ++++----- .../Strings/ro-RO/Resources.resx | 30 ++++----- .../Strings/ru-RU/Resources.resx | 32 ++++----- .../Strings/sk-SK/Resources.resx | 28 ++++---- .../Strings/sl-SI/Resources.resx | 28 ++++---- .../Strings/sr-Latn-RS/Resources.resx | 26 ++++---- .../Strings/sv-SE/Resources.resx | 30 ++++----- .../Strings/th-TH/Resources.resx | 32 ++++----- .../Strings/tr-TR/Resources.resx | 30 ++++----- .../Strings/uk-UA/Resources.resx | 32 ++++----- .../Strings/zh-CN/Resources.resx | 32 ++++----- .../Strings/zh-TW/Resources.resx | 32 ++++----- src/Foundry/Strings/ar-SA/Resources.resw | 38 +++++------ src/Foundry/Strings/bg-BG/Resources.resw | 38 +++++------ src/Foundry/Strings/cs-CZ/Resources.resw | 38 +++++------ src/Foundry/Strings/da-DK/Resources.resw | 38 +++++------ src/Foundry/Strings/de-DE/Resources.resw | 38 +++++------ src/Foundry/Strings/el-GR/Resources.resw | 38 +++++------ src/Foundry/Strings/es-ES/Resources.resw | 38 +++++------ src/Foundry/Strings/es-MX/Resources.resw | 38 +++++------ src/Foundry/Strings/et-EE/Resources.resw | 38 +++++------ src/Foundry/Strings/fi-FI/Resources.resw | 38 +++++------ src/Foundry/Strings/fr-CA/Resources.resw | 38 +++++------ src/Foundry/Strings/fr-FR/Resources.resw | 38 +++++------ src/Foundry/Strings/he-IL/Resources.resw | 38 +++++------ src/Foundry/Strings/hr-HR/Resources.resw | 38 +++++------ src/Foundry/Strings/hu-HU/Resources.resw | 38 +++++------ src/Foundry/Strings/it-IT/Resources.resw | 38 +++++------ src/Foundry/Strings/ja-JP/Resources.resw | 38 +++++------ src/Foundry/Strings/ko-KR/Resources.resw | 65 +++++++++++++------ src/Foundry/Strings/lt-LT/Resources.resw | 38 +++++------ src/Foundry/Strings/lv-LV/Resources.resw | 38 +++++------ src/Foundry/Strings/nb-NO/Resources.resw | 38 +++++------ src/Foundry/Strings/nl-NL/Resources.resw | 38 +++++------ src/Foundry/Strings/pl-PL/Resources.resw | 38 +++++------ src/Foundry/Strings/pt-BR/Resources.resw | 38 +++++------ src/Foundry/Strings/pt-PT/Resources.resw | 38 +++++------ src/Foundry/Strings/ro-RO/Resources.resw | 38 +++++------ src/Foundry/Strings/ru-RU/Resources.resw | 38 +++++------ src/Foundry/Strings/sk-SK/Resources.resw | 38 +++++------ src/Foundry/Strings/sl-SI/Resources.resw | 38 +++++------ src/Foundry/Strings/sr-Latn-RS/Resources.resw | 38 +++++------ src/Foundry/Strings/sv-SE/Resources.resw | 38 +++++------ src/Foundry/Strings/th-TH/Resources.resw | 38 +++++------ src/Foundry/Strings/tr-TR/Resources.resw | 38 +++++------ src/Foundry/Strings/uk-UA/Resources.resw | 38 +++++------ src/Foundry/Strings/zh-CN/Resources.resw | 38 +++++------ src/Foundry/Strings/zh-TW/Resources.resw | 38 +++++------ 72 files changed, 1256 insertions(+), 1229 deletions(-) diff --git a/src/Foundry.Deploy/Strings/ar-SA/Resources.resx b/src/Foundry.Deploy/Strings/ar-SA/Resources.resx index da1303aa..92dff1c2 100644 --- a/src/Foundry.Deploy/Strings/ar-SA/Resources.resx +++ b/src/Foundry.Deploy/Strings/ar-SA/Resources.resx @@ -509,25 +509,25 @@ تم تجهيز مساعد تسجيل Autopilot التفاعلي (محاكاة). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + تأكيد استرداد نظام التشغيل + سيحتفظ استرداد نظام التشغيل بأقسام EFI وMSR والاسترداد، ثم يستبدل قسم Windows على القرص المحدد. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +القرص: {0} +الموديل: {1} +الحافلة: {2} +الحجم: {3} +نظام التشغيل: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +هل تريد الاستمرار في استرداد نظام التشغيل؟ + توفير استرداد نظام التشغيل + جارٍ توفير استرداد نظام التشغيل... + جارٍ إدخال حمولة استرداد نظام التشغيل... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + جارٍ تسجيل قائمة تمهيد استرداد نظام التشغيل... + تم تعطيل استرداد نظام التشغيل. + يتم تخطي توفير استرداد نظام التشغيل في وضع الاسترداد. + تم توفير استرداد نظام التشغيل. + تم توفير استرداد نظام التشغيل (محاكاة). استرداد Foundry إعادة نشر Windows diff --git a/src/Foundry.Deploy/Strings/bg-BG/Resources.resx b/src/Foundry.Deploy/Strings/bg-BG/Resources.resx index 07addd97..a8c060ac 100644 --- a/src/Foundry.Deploy/Strings/bg-BG/Resources.resx +++ b/src/Foundry.Deploy/Strings/bg-BG/Resources.resx @@ -509,25 +509,25 @@ Интерактивният помощник за регистрация на Autopilot е подготвен (симулация). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Потвърдете възстановяването на ОС + Възстановяването на ОС ще запази EFI, MSR и дяловете за възстановяване, след което ще замени дяла на Windows на избрания диск. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Диск: {0} +Модел: {1} +Автобус: {2} +Размер: {3} +Операционна система: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Продължаване с възстановяване на ОС? + Осигуряване на възстановяване на ОС + Осигуряване на възстановяване на ОС... + Инжектиране на полезния товар за възстановяване на ОС... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Регистриране на менюто за стартиране на възстановяване на ОС... + Възстановяването на ОС е деактивирано. + Осигуряването на възстановяване на ОС се пропуска в режим на възстановяване. + Осигурено възстановяване на ОС. + Осигурено възстановяване на ОС (симулация). Възстановяване Foundry Преинсталиране Windows diff --git a/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx b/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx index 43d7500a..ea50e06c 100644 --- a/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx +++ b/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx @@ -509,25 +509,25 @@ Pokračovat v nasazování? Interaktivní asistent registrace Autopilotu byl připraven (simulace). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Potvrďte obnovení OS + Obnova OS zachová oddíly EFI, MSR a Recovery a poté nahradí oddíl Windows na vybraném disku. Disk: {0} Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Autobus: {2} +Velikost: {3} +Operační systém: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Pokračovat v obnově OS? + Připravit obnovení OS + Provádění obnovy OS... + Vkládání dat pro obnovu OS... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrace spouštěcí nabídky OS Recovery... + Obnovení OS je zakázáno. + Poskytování obnovy OS je v režimu obnovy přeskočeno. + Obnovení OS zajištěno. + Bylo zajištěno obnovení OS (simulace). Obnova Foundry Znovu nasadit Windows diff --git a/src/Foundry.Deploy/Strings/da-DK/Resources.resx b/src/Foundry.Deploy/Strings/da-DK/Resources.resx index 0e932a85..037637ee 100644 --- a/src/Foundry.Deploy/Strings/da-DK/Resources.resx +++ b/src/Foundry.Deploy/Strings/da-DK/Resources.resx @@ -509,25 +509,25 @@ Vil du fortsætte med implementeringen? Den interaktive Autopilot-registreringsassistent er klargjort (simulation). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Bekræft OS-gendannelse + OS Recovery bevarer EFI-, MSR- og Recovery-partitionerne og erstatter derefter Windows-partitionen på den valgte disk. Disk: {0} Model: {1} Bus: {2} -Size: {3} -Operating system: {4} +Størrelse: {3} +Operativsystem: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Vil du fortsætte med OS Recovery? + Provision OS Gendannelse + Klargøring af OS-gendannelse... + Injicerer OS-gendannelsesnyttelast... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrerer OS Recovery boot-menu... + OS-gendannelse er deaktiveret. + Klargøring af OS-gendannelse springes over i gendannelsestilstand. + OS-gendannelse klargjort. + OS-gendannelse klargjort (simulering). Foundry-gendannelse Genudrul Windows diff --git a/src/Foundry.Deploy/Strings/de-DE/Resources.resx b/src/Foundry.Deploy/Strings/de-DE/Resources.resx index 6e2b8532..877ab358 100644 --- a/src/Foundry.Deploy/Strings/de-DE/Resources.resx +++ b/src/Foundry.Deploy/Strings/de-DE/Resources.resx @@ -509,25 +509,25 @@ Mit der Bereitstellung fortfahren? Der interaktive Autopilot-Registrierungsassistent wurde bereitgestellt (Simulation). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Bestätigen Sie die Betriebssystemwiederherstellung + OS Recovery behält die EFI-, MSR- und Wiederherstellungspartitionen bei und ersetzt dann die Windows-Partition auf der ausgewählten Festplatte. -Disk: {0} -Model: {1} +Datenträger: {0} +Modell: {1} Bus: {2} -Size: {3} -Operating system: {4} +Größe: {3} +Betriebssystem: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Mit der Betriebssystemwiederherstellung fortfahren? + Bereitstellen der Betriebssystemwiederherstellung + Betriebssystemwiederherstellung wird bereitgestellt... + Nutzlast für die Betriebssystemwiederherstellung wird eingefügt... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Das Bootmenü für die Betriebssystemwiederherstellung wird registriert... + Die Betriebssystemwiederherstellung ist deaktiviert. + Die Bereitstellung der Betriebssystemwiederherstellung wird im Wiederherstellungsmodus übersprungen. + Betriebssystemwiederherstellung bereitgestellt. + Betriebssystemwiederherstellung bereitgestellt (Simulation). Foundry-Wiederherstellung Windows neu bereitstellen diff --git a/src/Foundry.Deploy/Strings/el-GR/Resources.resx b/src/Foundry.Deploy/Strings/el-GR/Resources.resx index 479c98ac..ffb2cfba 100644 --- a/src/Foundry.Deploy/Strings/el-GR/Resources.resx +++ b/src/Foundry.Deploy/Strings/el-GR/Resources.resx @@ -509,25 +509,25 @@ Ο διαδραστικός βοηθός εγγραφής Autopilot προετοιμάστηκε (προσομοίωση). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Επιβεβαιώστε την ανάκτηση λειτουργικού συστήματος + Το OS Recovery θα διατηρήσει τα διαμερίσματα EFI, MSR και Recovery και, στη συνέχεια, θα αντικαταστήσει το διαμέρισμα των Windows στον επιλεγμένο δίσκο. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Δίσκος: {0} +Μοντέλο: {1} +Λεωφορείο: {2} +Μέγεθος: {3} +Λειτουργικό σύστημα: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Συνέχεια με το OS Recovery; + Παροχή ανάκτησης λειτουργικού συστήματος + Παροχή ανάκτησης λειτουργικού συστήματος... + Έγχυση ωφέλιμου φορτίου ανάκτησης λειτουργικού συστήματος... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Εγγραφή του μενού εκκίνησης του OS Recovery... + Η ανάκτηση λειτουργικού συστήματος είναι απενεργοποιημένη. + Η παροχή ανάκτησης λειτουργικού συστήματος παραλείπεται στη λειτουργία ανάκτησης. + Παρέχεται ανάκτηση λειτουργικού συστήματος. + Παρέχεται ανάκτηση λειτουργικού συστήματος (προσομοίωση). Ανάκτηση Foundry Επανεγκατάσταση Windows diff --git a/src/Foundry.Deploy/Strings/es-ES/Resources.resx b/src/Foundry.Deploy/Strings/es-ES/Resources.resx index 09e1d804..75bdcc85 100644 --- a/src/Foundry.Deploy/Strings/es-ES/Resources.resx +++ b/src/Foundry.Deploy/Strings/es-ES/Resources.resx @@ -509,25 +509,25 @@ Sistema operativo: {4} Asistente de registro interactivo de Autopilot preparado (simulación). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Confirmar la recuperación del sistema operativo + OS Recovery preservará las particiones EFI, MSR y Recovery, luego reemplazará la partición de Windows en el disco seleccionado. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Disco: {0} +Modelo: {1} +Autobús: {2} +Tamaño: {3} +Sistema operativo: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +¿Continuar con la recuperación del sistema operativo? + Aprovisionar recuperación del sistema operativo + Aprovisionando recuperación del sistema operativo... + Inyectando carga útil de recuperación del sistema operativo... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrando el menú de inicio de OS Recovery... + La recuperación del sistema operativo está deshabilitada. + El aprovisionamiento de OS Recovery se omite en el modo de recuperación. + Recuperación del sistema operativo aprovisionada. + Recuperación del sistema operativo aprovisionada (simulación). Recuperación Foundry Reinstalar Windows diff --git a/src/Foundry.Deploy/Strings/es-MX/Resources.resx b/src/Foundry.Deploy/Strings/es-MX/Resources.resx index bbaebfd2..8c8c566d 100644 --- a/src/Foundry.Deploy/Strings/es-MX/Resources.resx +++ b/src/Foundry.Deploy/Strings/es-MX/Resources.resx @@ -509,25 +509,25 @@ Sistema operativo: {4} Asistente de registro interactivo de Autopilot preparado (simulación). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Confirmar la recuperación del sistema operativo + OS Recovery preservará las particiones EFI, MSR y Recovery, luego reemplazará la partición de Windows en el disco seleccionado. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Disco: {0} +Modelo: {1} +Autobús: {2} +Tamaño: {3} +Sistema operativo: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +¿Continuar con la recuperación del sistema operativo? + Aprovisionar recuperación del sistema operativo + Aprovisionando recuperación del sistema operativo... + Inyectando carga útil de recuperación del sistema operativo... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrando el menú de inicio de OS Recovery... + La recuperación del sistema operativo está deshabilitada. + El aprovisionamiento de OS Recovery se omite en el modo de recuperación. + Recuperación del sistema operativo aprovisionada. + Recuperación del sistema operativo aprovisionada (simulación). Recuperación Foundry Reinstalar Windows diff --git a/src/Foundry.Deploy/Strings/et-EE/Resources.resx b/src/Foundry.Deploy/Strings/et-EE/Resources.resx index 2543ff26..3430696e 100644 --- a/src/Foundry.Deploy/Strings/et-EE/Resources.resx +++ b/src/Foundry.Deploy/Strings/et-EE/Resources.resx @@ -509,25 +509,25 @@ Kas jätkata juurutamisega? Interaktiivne Autopiloti registreerimisabiline on ette valmistatud (simulatsioon). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Kinnitage OS-i taastamine + OS-i taastamine säilitab EFI-, MSR- ja taastepartitsioonid ning asendab seejärel Windowsi partitsiooni valitud kettal. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Ketas: {0} +Mudel: {1} +Buss: {2} +Suurus: {3} +Operatsioonisüsteem: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Kas jätkata OS-i taastamisega? + OS-i taastamise pakkumine + OS-i taastamise ettevalmistamine... + OS-i taastamise kasuliku koormuse sisestamine... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + OS-i taastamise alglaadimismenüü registreerimine... + OS-i taastamine on keelatud. + OS-i taastamise varustamine jäetakse taasterežiimis vahele. + OS-i taastamine on ette nähtud. + OS-i taastamine on ette nähtud (simulatsioon). Foundry taaste Juuruta Windows uuesti diff --git a/src/Foundry.Deploy/Strings/fi-FI/Resources.resx b/src/Foundry.Deploy/Strings/fi-FI/Resources.resx index 95f39ecb..a84faeaa 100644 --- a/src/Foundry.Deploy/Strings/fi-FI/Resources.resx +++ b/src/Foundry.Deploy/Strings/fi-FI/Resources.resx @@ -509,25 +509,25 @@ Jatketaanko käyttöönottoa? Vuorovaikutteinen Autopilot-rekisteröintiavustaja on valmisteltu (simulointi). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Vahvista käyttöjärjestelmän palautus + OS Recovery säilyttää EFI-, MSR- ja Recovery-osiot ja korvaa sitten Windows-osion valitulla levyllä. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Levy: {0} +Malli: {1} +Bussi: {2} +Koko: {3} +Käyttöjärjestelmä: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Jatketaanko käyttöjärjestelmän palautusta? + Tarjoa käyttöjärjestelmän palautus + Otetaan käyttöön käyttöjärjestelmän palautus... + Lisätään käyttöjärjestelmän palautusta... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Rekisteröidään OS Recovery -käynnistysvalikkoa... + Käyttöjärjestelmän palautus on poistettu käytöstä. + Käyttöjärjestelmän palautuksen hallinta ohitetaan palautustilassa. + Käyttöjärjestelmän palautus varattu. + Käyttöjärjestelmän palautus varusteltu (simulaatio). Foundry-palautus Asenna Windows uudelleen diff --git a/src/Foundry.Deploy/Strings/fr-CA/Resources.resx b/src/Foundry.Deploy/Strings/fr-CA/Resources.resx index 9adaab28..15e50157 100644 --- a/src/Foundry.Deploy/Strings/fr-CA/Resources.resx +++ b/src/Foundry.Deploy/Strings/fr-CA/Resources.resx @@ -509,8 +509,8 @@ Continuer le déploiement? Assistant d'enregistrement Autopilot interactif préparé (simulation). - Confirmer OS Recovery - OS Recovery conservera les partitions EFI, MSR et Recovery, puis remplacera la partition Windows du disque sélectionné. + Confirmer la récupération du SE + La récupération du SE conservera les partitions EFI, MSR et Recovery, puis remplacera la partition Windows sur le disque sélectionné. Disque : {0} Modèle : {1} @@ -518,16 +518,16 @@ Bus : {2} Taille : {3} Système d’exploitation : {4} -Continuer avec OS Recovery ? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Continuer avec la récupération du SE ? + Provisionner la récupération du SE + Provisionnement de la récupération du SE... + Injection du payload de récupération du SE... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Enregistrement du menu de démarrage de récupération du SE... + La récupération du SE est désactivée. + Le provisionnement de la récupération du SE est ignoré en mode récupération. + Récupération du SE provisionnée. + Récupération du SE provisionnée (simulation). Récupération Foundry Redéployer Windows diff --git a/src/Foundry.Deploy/Strings/fr-FR/Resources.resx b/src/Foundry.Deploy/Strings/fr-FR/Resources.resx index 34ce6c22..96f5c0d7 100644 --- a/src/Foundry.Deploy/Strings/fr-FR/Resources.resx +++ b/src/Foundry.Deploy/Strings/fr-FR/Resources.resx @@ -509,8 +509,8 @@ Continuer le déploiement ? Assistant d'enregistrement Autopilot interactif préparé (simulation). - Confirmer OS Recovery - OS Recovery conservera les partitions EFI, MSR et Recovery, puis remplacera la partition Windows du disque sélectionné. + Confirmer la récupération de l’OS + La récupération de l’OS conservera les partitions EFI, MSR et Recovery, puis remplacera la partition Windows sur le disque sélectionné. Disque : {0} Modèle : {1} @@ -518,16 +518,16 @@ Bus : {2} Taille : {3} Système d’exploitation : {4} -Continuer avec OS Recovery ? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Continuer avec la récupération de l’OS ? + Provisionner la récupération de l’OS + Provisionnement de la récupération de l’OS... + Injection du payload de récupération de l’OS... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Enregistrement du menu de démarrage de récupération de l’OS... + La récupération de l’OS est désactivée. + Le provisionnement de la récupération de l’OS est ignoré en mode récupération. + Récupération de l’OS provisionnée. + Récupération de l’OS provisionnée (simulation). Récupération Foundry Redéployer Windows diff --git a/src/Foundry.Deploy/Strings/he-IL/Resources.resx b/src/Foundry.Deploy/Strings/he-IL/Resources.resx index cfdc5a06..9a00d5ed 100644 --- a/src/Foundry.Deploy/Strings/he-IL/Resources.resx +++ b/src/Foundry.Deploy/Strings/he-IL/Resources.resx @@ -509,25 +509,25 @@ ErrorCode=0x80070005 מסייע הרישום האינטראקטיבי של Autopilot הוכן (סימולציה). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + אשר את שחזור מערכת ההפעלה + שחזור מערכת ההפעלה ישמור מחיצות EFI, MSR ושחזור, ולאחר מכן יחליף את מחיצת Windows בדיסק הנבחר. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +דיסק: {0} +דגם: {1} +אוטובוס: {2} +גודל: {3} +מערכת הפעלה: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +להמשיך עם שחזור מערכת ההפעלה? + שחזור מערכת הפעלה אספקה + הקצאת שחזור מערכת ההפעלה... + מזריקים מטען שחזור מערכת ההפעלה... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + רושם את תפריט האתחול של שחזור מערכת ההפעלה... + שחזור מערכת ההפעלה מושבת. + דילוג על הקצאת שחזור מערכת ההפעלה במצב שחזור. + שחזור מערכת הפעלה הוקצה. + התאוששות מערכת הפעלה הוגדרה (סימולציה). שחזור Foundry פריסה מחדש של Windows diff --git a/src/Foundry.Deploy/Strings/hr-HR/Resources.resx b/src/Foundry.Deploy/Strings/hr-HR/Resources.resx index 38ed1d94..2e656fca 100644 --- a/src/Foundry.Deploy/Strings/hr-HR/Resources.resx +++ b/src/Foundry.Deploy/Strings/hr-HR/Resources.resx @@ -509,25 +509,25 @@ Nastaviti s implementacijom? Interaktivni pomoćnik za registraciju Autopilota je pripremljen (simulacija). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Potvrdite oporavak OS-a + OS Recovery će sačuvati EFI, MSR i particije za oporavak, a zatim zamijeniti Windows particiju na odabranom disku. Disk: {0} Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Autobus: {2} +Veličina: {3} +Operativni sustav: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Nastaviti s oporavkom OS-a? + Pružanje oporavka OS-a + Omogućavanje oporavka OS-a... + Ubacivanje nosivosti oporavka OS-a... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registriranje izbornika za pokretanje OS Recovery... + Oporavak OS-a je onemogućen. + Omogućavanje oporavka OS-a preskočeno je u načinu oporavka. + Omogućen oporavak OS-a. + Osiguran oporavak OS-a (simulacija). Foundry oporavak Ponovno uvedi Windows diff --git a/src/Foundry.Deploy/Strings/hu-HU/Resources.resx b/src/Foundry.Deploy/Strings/hu-HU/Resources.resx index 251a42e3..a7b77829 100644 --- a/src/Foundry.Deploy/Strings/hu-HU/Resources.resx +++ b/src/Foundry.Deploy/Strings/hu-HU/Resources.resx @@ -509,25 +509,25 @@ Folytatja a telepítést? Az interaktív Autopilot regisztrációs segéd előkészítve (szimuláció). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Erősítse meg az operációs rendszer helyreállítását + Az OS Recovery megőrzi az EFI, MSR és Recovery partíciókat, majd lecseréli a Windows partíciót a kiválasztott lemezen. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Lemez: {0} +Modell: {1} +Busz: {2} +Méret: {3} +Operációs rendszer: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Folytatja az OS helyreállítást? + Az operációs rendszer helyreállításának biztosítása + Az operációs rendszer helyreállításának kiépítése... + OS Recovery hasznos terhelés beadása... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Az OS Recovery rendszerindító menü regisztrálása... + Az operációs rendszer helyreállítása le van tiltva. + Az OS-helyreállítás kiépítése helyreállítási módban kimarad. + Az operációs rendszer helyreállítása biztosított. + Az operációs rendszer helyreállítása biztosított (szimuláció). Foundry-helyreállítás Windows újratelepítése diff --git a/src/Foundry.Deploy/Strings/it-IT/Resources.resx b/src/Foundry.Deploy/Strings/it-IT/Resources.resx index 30e40eff..8b50e38b 100644 --- a/src/Foundry.Deploy/Strings/it-IT/Resources.resx +++ b/src/Foundry.Deploy/Strings/it-IT/Resources.resx @@ -509,25 +509,25 @@ Continuare con la distribuzione? Assistente di registrazione interattiva di Autopilot preparato (simulazione). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Conferma il ripristino del sistema operativo + OS Recovery conserverà le partizioni EFI, MSR e Recovery, quindi sostituirà la partizione Windows sul disco selezionato. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Disco: {0} +Modello: {1} +Autobus: {2} +Taglia: {3} +Sistema operativo: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Continuare con il ripristino del sistema operativo? + Fornire il ripristino del sistema operativo + Provisioning del ripristino del sistema operativo... + Inserimento del payload di ripristino del sistema operativo in corso... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrazione del menu di avvio di ripristino del sistema operativo in corso... + Il ripristino del sistema operativo è disabilitato. + Il provisioning del ripristino del sistema operativo viene ignorato in modalità di ripristino. + Effettuato il provisioning del ripristino del sistema operativo. + Provisioning del ripristino del sistema operativo (simulazione). Ripristino Foundry Reinstalla Windows diff --git a/src/Foundry.Deploy/Strings/ja-JP/Resources.resx b/src/Foundry.Deploy/Strings/ja-JP/Resources.resx index 082ccd0f..d32306cd 100644 --- a/src/Foundry.Deploy/Strings/ja-JP/Resources.resx +++ b/src/Foundry.Deploy/Strings/ja-JP/Resources.resx @@ -509,25 +509,25 @@ 対話型 Autopilot 登録アシスタントをステージングしました (シミュレーション)。 - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + OSリカバリの確認 + OS リカバリでは、EFI、MSR、およびリカバリ パーティションが保存され、選択したディスク上の Windows パーティションが置き換えられます。 -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +ディスク: {0} +モデル: {1} +バス: {2} +サイズ: {3} +オペレーティング システム: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +OS リカバリを続行しますか? + OS リカバリのプロビジョニング + OS リカバリをプロビジョニングしています... + OS リカバリ ペイロードを挿入しています... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + OS リカバリ ブート メニューを登録しています... + OS リカバリは無効になっています。 + OS リカバリ プロビジョニングはリカバリ モードではスキップされます。 + OS リカバリがプロビジョニングされました。 + OS リカバリがプロビジョニングされました (シミュレーション)。 Foundry 回復 Windows を再展開 diff --git a/src/Foundry.Deploy/Strings/ko-KR/Resources.resx b/src/Foundry.Deploy/Strings/ko-KR/Resources.resx index a716c3cc..9f9d9084 100644 --- a/src/Foundry.Deploy/Strings/ko-KR/Resources.resx +++ b/src/Foundry.Deploy/Strings/ko-KR/Resources.resx @@ -509,25 +509,25 @@ 대화형 Autopilot 등록 도우미가 준비되었습니다(시뮬레이션). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + OS 복구 확인 + OS 복구는 EFI, MSR 및 복구 파티션을 보존한 다음 선택한 디스크의 Windows 파티션을 교체합니다. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +디스크: {0} +모델: {1} +버스: {2} +크기: {3} +운영 체제: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +OS 복구를 계속하시겠습니까? + OS 복구 프로비저닝 + OS 복구 프로비저닝 중... + OS 복구 페이로드를 삽입하는 중... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + OS 복구 부팅 메뉴 등록 중... + OS 복구가 비활성화되었습니다. + 복구 모드에서는 OS 복구 프로비저닝을 건너뜁니다. + OS 복구가 프로비저닝되었습니다. + OS 복구 프로비저닝됨(시뮬레이션) Foundry 복구 Windows 재배포 diff --git a/src/Foundry.Deploy/Strings/lt-LT/Resources.resx b/src/Foundry.Deploy/Strings/lt-LT/Resources.resx index aa0270de..2863f198 100644 --- a/src/Foundry.Deploy/Strings/lt-LT/Resources.resx +++ b/src/Foundry.Deploy/Strings/lt-LT/Resources.resx @@ -509,25 +509,25 @@ Tęsti diegimą? Interaktyvus Autopilot registracijos pagalbininkas paruoštas (modeliavimas). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Patvirtinkite OS atkūrimą + OS atkūrimas išsaugos EFI, MSR ir Recovery skaidinius, tada pakeis Windows skaidinį pasirinktame diske. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Diskas: {0} +Modelis: {1} +Autobusas: {2} +Dydis: {3} +Operacinė sistema: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Tęsti OS atkūrimą? + Pateikite OS atkūrimą + Teikiamas OS atkūrimas... + Įvedama OS atkūrimo naudingoji apkrova... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registruojamas OS atkūrimo įkrovos meniu... + OS atkūrimas išjungtas. + Atkūrimo režimu OS atkūrimo aprūpinimas praleidžiamas. + Suteiktas OS atkūrimas. + OS atkūrimas numatytas (modeliavimas). Foundry atkūrimas Iš naujo diegti Windows diff --git a/src/Foundry.Deploy/Strings/lv-LV/Resources.resx b/src/Foundry.Deploy/Strings/lv-LV/Resources.resx index e8c1774c..9598649c 100644 --- a/src/Foundry.Deploy/Strings/lv-LV/Resources.resx +++ b/src/Foundry.Deploy/Strings/lv-LV/Resources.resx @@ -509,25 +509,25 @@ Vai turpināt izvietošanu? Interaktīvais Autopilot reģistrācijas palīgs ir sagatavots (simulācija). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Apstipriniet OS atkopšanu + OS atkopšana saglabās EFI, MSR un Recovery nodalījumus, pēc tam nomainīs Windows nodalījumu atlasītajā diskā. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Disks: {0} +Modelis: {1} +Autobuss: {2} +Izmērs: {3} +Operētājsistēma: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Vai turpināt ar OS atkopšanu? + Nodrošiniet OS atkopšanu + Notiek OS atkopšanas nodrošināšana... + Notiek OS atkopšanas derīgās slodzes ievadīšana... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Notiek OS atkopšanas sāknēšanas izvēlnes reģistrācija... + OS atkopšana ir atspējota. + Atkopšanas režīmā OS atkopšanas nodrošināšana tiek izlaista. + Nodrošināta OS atkopšana. + Nodrošināta OS atkopšana (simulācija). Foundry atkopšana Pārizvietot Windows diff --git a/src/Foundry.Deploy/Strings/nb-NO/Resources.resx b/src/Foundry.Deploy/Strings/nb-NO/Resources.resx index e3e6899c..54e5199b 100644 --- a/src/Foundry.Deploy/Strings/nb-NO/Resources.resx +++ b/src/Foundry.Deploy/Strings/nb-NO/Resources.resx @@ -509,25 +509,25 @@ Vil du fortsette med distribusjonen? Den interaktive Autopilot-registreringsassistenten er klargjort (simulering). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Bekreft OS-gjenoppretting + OS Recovery vil bevare EFI-, MSR- og Recovery-partisjonene, og erstatter deretter Windows-partisjonen på den valgte disken. Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Modell: {1} +Buss: {2} +Størrelse: {3} +Operativsystem: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Vil du fortsette med OS-gjenoppretting? + Klargjør OS-gjenoppretting + Klargjør OS-gjenoppretting... + Injiserer nyttelast for OS-gjenoppretting... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrerer OS Recovery oppstartsmeny... + OS-gjenoppretting er deaktivert. + Klargjøring av OS-gjenoppretting hoppes over i gjenopprettingsmodus. + OS-gjenoppretting klargjort. + OS-gjenoppretting klargjort (simulering). Foundry-gjenoppretting Rull ut Windows på nytt diff --git a/src/Foundry.Deploy/Strings/nl-NL/Resources.resx b/src/Foundry.Deploy/Strings/nl-NL/Resources.resx index 4cb83201..66d41373 100644 --- a/src/Foundry.Deploy/Strings/nl-NL/Resources.resx +++ b/src/Foundry.Deploy/Strings/nl-NL/Resources.resx @@ -509,25 +509,25 @@ Doorgaan met de implementatie? Interactieve Autopilot-registratieassistent voorbereid (simulatie). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Bevestig OS-herstel + OS Recovery behoudt de EFI-, MSR- en herstelpartities en vervangt vervolgens de Windows-partitie op de geselecteerde schijf. -Disk: {0} +Schijf: {0} Model: {1} Bus: {2} -Size: {3} -Operating system: {4} +Maat: {3} +Besturingssysteem: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Doorgaan met OS-herstel? + OS-herstel inrichten + Besturingssysteemherstel inrichten... + Bezig met injecteren van de OS Recovery-payload... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registreren van het opstartmenu van OS Recovery... + Besturingssysteemherstel is uitgeschakeld. + Het inrichten van OS Recovery wordt overgeslagen in de herstelmodus. + OS-herstel ingericht. + OS Recovery ingericht (simulatie). Foundry-herstel Windows opnieuw uitrollen diff --git a/src/Foundry.Deploy/Strings/pl-PL/Resources.resx b/src/Foundry.Deploy/Strings/pl-PL/Resources.resx index 6be67492..8bad25ff 100644 --- a/src/Foundry.Deploy/Strings/pl-PL/Resources.resx +++ b/src/Foundry.Deploy/Strings/pl-PL/Resources.resx @@ -509,25 +509,25 @@ Kontynuować wdrażanie? Interaktywny asystent rejestracji Autopilot został przygotowany (symulacja). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Potwierdź przywrócenie systemu operacyjnego + Funkcja OS Recovery zachowa partycje EFI, MSR i Recovery, a następnie zastąpi partycję Windows na wybranym dysku. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Dysk: {0} +Modelka: {1} +Autobus: {2} +Rozmiar: {3} +System operacyjny: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Kontynuować odzyskiwanie systemu operacyjnego? + Zapewnij odzyskiwanie systemu operacyjnego + Udostępnianie odzyskiwania systemu operacyjnego... + Wstrzykiwanie ładunku odzyskiwania systemu operacyjnego... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Rejestrowanie menu startowego odzyskiwania systemu operacyjnego... + Odzyskiwanie systemu operacyjnego jest wyłączone. + Udostępnianie systemu operacyjnego OS Recovery jest pomijane w trybie odzyskiwania. + Zapewnione odzyskiwanie systemu operacyjnego. + Zapewnione odzyskiwanie systemu operacyjnego (symulacja). Odzyskiwanie Foundry Wdróż Windows ponownie diff --git a/src/Foundry.Deploy/Strings/pt-BR/Resources.resx b/src/Foundry.Deploy/Strings/pt-BR/Resources.resx index f18795ba..31f8c3ae 100644 --- a/src/Foundry.Deploy/Strings/pt-BR/Resources.resx +++ b/src/Foundry.Deploy/Strings/pt-BR/Resources.resx @@ -509,25 +509,25 @@ Continuar com a implantação? Assistente de registro interativo do Autopilot preparado (simulação). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Confirme a recuperação do sistema operacional + O OS Recovery preservará as partições EFI, MSR e Recovery e, em seguida, substituirá a partição do Windows no disco selecionado. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Disco: {0} +Modelo: {1} +Ônibus: {2} +Tamanho: {3} +Sistema operacional: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Continuar com a recuperação do sistema operacional? + Provisionar recuperação de sistema operacional + Provisionando recuperação do sistema operacional... + Injetando carga útil de recuperação do sistema operacional... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrando o menu de inicialização do OS Recovery... + A recuperação do sistema operacional está desativada. + O provisionamento do OS Recovery é ignorado no modo de recuperação. + Recuperação do sistema operacional provisionada. + Recuperação de SO provisionada (simulação). Recuperação Foundry Reimplantar Windows diff --git a/src/Foundry.Deploy/Strings/pt-PT/Resources.resx b/src/Foundry.Deploy/Strings/pt-PT/Resources.resx index ca552abd..4c685192 100644 --- a/src/Foundry.Deploy/Strings/pt-PT/Resources.resx +++ b/src/Foundry.Deploy/Strings/pt-PT/Resources.resx @@ -509,25 +509,25 @@ Continuar com a implantação? Assistente de registo interativo do Autopilot preparado (simulação). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Confirme a recuperação do sistema operacional + O OS Recovery preservará as partições EFI, MSR e Recovery e, em seguida, substituirá a partição do Windows no disco selecionado. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Disco: {0} +Modelo: {1} +Ônibus: {2} +Tamanho: {3} +Sistema operacional: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Continuar com a recuperação do sistema operacional? + Provisionar recuperação de sistema operacional + Provisionando recuperação do sistema operacional... + Injetando carga útil de recuperação do sistema operacional... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrando o menu de inicialização do OS Recovery... + A recuperação do sistema operacional está desativada. + O provisionamento do OS Recovery é ignorado no modo de recuperação. + Recuperação do sistema operacional provisionada. + Recuperação de SO provisionada (simulação). Recuperação Foundry Reimplementar Windows diff --git a/src/Foundry.Deploy/Strings/ro-RO/Resources.resx b/src/Foundry.Deploy/Strings/ro-RO/Resources.resx index 5f5f40ed..026c3210 100644 --- a/src/Foundry.Deploy/Strings/ro-RO/Resources.resx +++ b/src/Foundry.Deploy/Strings/ro-RO/Resources.resx @@ -509,25 +509,25 @@ Continuați cu implementarea? Asistentul de înregistrare interactivă Autopilot a fost pregătit (simulare). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Confirmați recuperarea sistemului de operare + OS Recovery va păstra partițiile EFI, MSR și Recovery, apoi va înlocui partiția Windows de pe discul selectat. -Disk: {0} +Disc: {0} Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Autobuz: {2} +Dimensiune: {3} +Sistem de operare: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Continuați cu OS Recovery? + Asigurați recuperarea sistemului de operare + Se asigură recuperarea sistemului de operare... + Se injectează sarcina utilă de recuperare a sistemului de operare... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Se înregistrează meniul de pornire OS Recovery... + Recuperarea sistemului de operare este dezactivată. + Aprovizionarea OS Recovery este omisă în modul de recuperare. + Recuperarea sistemului de operare a fost asigurată. + Recuperare OS asigurată (simulare). Recuperare Foundry Redeplasați Windows diff --git a/src/Foundry.Deploy/Strings/ru-RU/Resources.resx b/src/Foundry.Deploy/Strings/ru-RU/Resources.resx index 7a7f0b78..691598f2 100644 --- a/src/Foundry.Deploy/Strings/ru-RU/Resources.resx +++ b/src/Foundry.Deploy/Strings/ru-RU/Resources.resx @@ -509,25 +509,25 @@ Интерактивный помощник регистрации Autopilot подготовлен (симуляция). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Подтвердить восстановление ОС + Восстановление ОС сохранит разделы EFI, MSR и Recovery, а затем заменит раздел Windows на выбранном диске. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Диск: {0} +Модель: {1} +Автобус: {2} +Размер: {3} +Операционная система: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Продолжить восстановление ОС? + Обеспечение восстановления ОС + Обеспечение восстановления ОС... + Внедрение полезных данных восстановления ОС... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Регистрация меню загрузки OS Recovery... + Восстановление ОС отключено. + В режиме восстановления подготовка ОС для восстановления пропускается. + Предусмотрено восстановление ОС. + Предусмотрено восстановление ОС (симуляция). Восстановление Foundry Развернуть Windows заново diff --git a/src/Foundry.Deploy/Strings/sk-SK/Resources.resx b/src/Foundry.Deploy/Strings/sk-SK/Resources.resx index d548ab6c..f0753975 100644 --- a/src/Foundry.Deploy/Strings/sk-SK/Resources.resx +++ b/src/Foundry.Deploy/Strings/sk-SK/Resources.resx @@ -509,25 +509,25 @@ Pokračovať v nasadzovaní? Interaktívny asistent registrácie Autopilotu bol pripravený (simulácia). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Potvrďte obnovenie OS + Obnova OS zachová oblasti EFI, MSR a Recovery a potom nahradí oblasť Windows na vybranom disku. Disk: {0} Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Autobus: {2} +Veľkosť: {3} +Operačný systém: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Pokračovať v obnove OS? + Poskytovanie obnovy OS + Poskytovanie obnovy OS... + Vkladá sa užitočné zaťaženie na obnovenie OS... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrácia zavádzacej ponuky obnovy OS... + Obnovenie OS je vypnuté. + Poskytovanie obnovy OS sa v režime obnovenia preskočí. + Zabezpečené obnovenie OS. + Zabezpečené obnovenie OS (simulácia). Obnova Foundry Znovu nasadiť Windows diff --git a/src/Foundry.Deploy/Strings/sl-SI/Resources.resx b/src/Foundry.Deploy/Strings/sl-SI/Resources.resx index 6e637eec..9429084d 100644 --- a/src/Foundry.Deploy/Strings/sl-SI/Resources.resx +++ b/src/Foundry.Deploy/Strings/sl-SI/Resources.resx @@ -509,25 +509,25 @@ Ali želite nadaljevati z uvajanjem? Interaktivni pomočnik za registracijo Autopilot je pripravljen (simulacija). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Potrdite obnovitev OS + OS Recovery bo ohranil particije EFI, MSR in Recovery, nato pa zamenjal particijo Windows na izbranem disku. Disk: {0} Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Avtobus: {2} +Velikost: {3} +Operacijski sistem: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Nadaljevati z obnovitvijo OS? + Zagotavljanje obnovitve OS + Omogočanje obnovitve OS ... + Vstavljanje tovora za obnovitev OS ... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registracija zagonskega menija za obnovitev OS ... + Obnovitev OS je onemogočena. + Omogočanje obnovitve OS je v obnovitvenem načinu preskočeno. + Zagotovljena obnovitev OS. + Zagotovljena obnovitev OS (simulacija). Obnovitev Foundry Znova uvedi Windows diff --git a/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx b/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx index dededd5c..d34eb9a0 100644 --- a/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx +++ b/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx @@ -509,25 +509,25 @@ Operativni sistem: {4} Interaktivni pomoćnik za registraciju Autopilota je pripremljen (simulacija). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Potvrdi oporavak OS-a + Oporavak OS-a će zadržati EFI, MSR i Recovery particije, zatim zameniti Windows particiju na izabranom disku. Disk: {0} Model: {1} Bus: {2} -Size: {3} -Operating system: {4} +Veličina: {3} +Operativni sistem: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Nastaviti sa oporavkom OS-a? + Pripremi oporavak OS-a + Priprema oporavka OS-a... + Ubacivanje sadržaja oporavka OS-a... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrovanje menija za pokretanje oporavka OS-a... + Oporavak OS-a je onemogućen. + Priprema oporavka OS-a se preskače u režimu oporavka. + Oporavak OS-a je pripremljen. + Oporavak OS-a je pripremljen (simulacija). Foundry oporavak Ponovo uvedi Windows diff --git a/src/Foundry.Deploy/Strings/sv-SE/Resources.resx b/src/Foundry.Deploy/Strings/sv-SE/Resources.resx index 43c810ab..1ee44347 100644 --- a/src/Foundry.Deploy/Strings/sv-SE/Resources.resx +++ b/src/Foundry.Deploy/Strings/sv-SE/Resources.resx @@ -509,25 +509,25 @@ Fortsätt med implementeringen? Den interaktiva Autopilot-registreringsassistenten har förberetts (simulering). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Bekräfta OS-återställning + OS Recovery bevarar EFI-, MSR- och Recovery-partitionerna och ersätter sedan Windows-partitionen på den valda disken. Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Modell: {1} +Buss: {2} +Storlek: {3} +Operativsystem: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Fortsätt med OS Recovery? + Provisionera OS-återställning + Provisionerar OS-återställning... + Injicerar nyttolast för OS-återställning... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Registrerar OS Recovery startmeny... + OS-återställning är inaktiverad. + Provisionering av OS-återställning hoppas över i återställningsläge. + OS-återställning tillhandahållen. + OS-återställning tillhandahållen (simulering). Foundry-återställning Distribuera Windows igen diff --git a/src/Foundry.Deploy/Strings/th-TH/Resources.resx b/src/Foundry.Deploy/Strings/th-TH/Resources.resx index 2011fd7d..5f09950e 100644 --- a/src/Foundry.Deploy/Strings/th-TH/Resources.resx +++ b/src/Foundry.Deploy/Strings/th-TH/Resources.resx @@ -509,25 +509,25 @@ จัดเตรียมผู้ช่วยลงทะเบียน Autopilot แบบโต้ตอบแล้ว (การจำลอง) - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + ยืนยันการกู้คืนระบบปฏิบัติการ + การกู้คืนระบบปฏิบัติการจะรักษาพาร์ติชัน EFI, MSR และการกู้คืนไว้ จากนั้นแทนที่พาร์ติชัน Windows บนดิสก์ที่เลือก -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +ดิสก์: {0} +รุ่น: {1} +รถบัส: {2} +ขนาด: {3} +ระบบปฏิบัติการ: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +ดำเนินการกู้คืน OS ต่อไปหรือไม่ + จัดเตรียมการกู้คืนระบบปฏิบัติการ + กำลังจัดเตรียมการกู้คืนระบบปฏิบัติการ... + กำลังเพิ่มเพย์โหลดการกู้คืน OS... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + กำลังลงทะเบียนเมนูบูต OS Recovery... + การกู้คืนระบบปฏิบัติการถูกปิดใช้งาน + การจัดสรรการกู้คืนระบบปฏิบัติการถูกข้ามไปในโหมดการกู้คืน + จัดเตรียมการกู้คืนระบบปฏิบัติการแล้ว + จัดเตรียมการกู้คืน OS (การจำลอง) กู้คืน Foundry ปรับใช้ Windows ใหม่ diff --git a/src/Foundry.Deploy/Strings/tr-TR/Resources.resx b/src/Foundry.Deploy/Strings/tr-TR/Resources.resx index 752e1537..4c8c19a7 100644 --- a/src/Foundry.Deploy/Strings/tr-TR/Resources.resx +++ b/src/Foundry.Deploy/Strings/tr-TR/Resources.resx @@ -509,25 +509,25 @@ Dağıtıma devam edilsin mi? Etkileşimli Autopilot kayıt yardımcısı hazırlandı (simülasyon). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + İşletim Sistemi Kurtarmayı Onayla + İşletim Sistemi Kurtarma, EFI, MSR ve Recovery bölümlerini koruyacak, ardından seçilen diskteki Windows bölümünü değiştirecektir. Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Modeli: {1} +Otobüs: {2} +Boyut: {3} +İşletim sistemi: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +İşletim Sistemi Kurtarma işlemine devam edilsin mi? + İşletim Sistemi Kurtarmanın Sağlanması + İşletim Sistemi Kurtarma Sağlanıyor... + İşletim Sistemi Kurtarma yükü enjekte ediliyor... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + İşletim Sistemi Kurtarma önyükleme menüsü kaydediliyor... + İşletim Sistemi Kurtarma devre dışı. + İşletim Sistemi Kurtarma provizyonu, kurtarma modunda atlanır. + İşletim Sistemi Kurtarma sağlandı. + İşletim Sistemi Kurtarmanın temel hazırlığı yapıldı (simülasyon). Foundry Kurtarma Windows'u yeniden dağıt diff --git a/src/Foundry.Deploy/Strings/uk-UA/Resources.resx b/src/Foundry.Deploy/Strings/uk-UA/Resources.resx index 07c1c628..9529e330 100644 --- a/src/Foundry.Deploy/Strings/uk-UA/Resources.resx +++ b/src/Foundry.Deploy/Strings/uk-UA/Resources.resx @@ -509,25 +509,25 @@ ErrorCode=0x80070005 Інтерактивний помічник реєстрації Autopilot підготовлено (симуляція). - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + Підтвердьте відновлення ОС + Відновлення ОС збереже розділи EFI, MSR і Recovery, а потім замінить розділ Windows на вибраному диску. -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +Диск: {0} +Модель: {1} +Автобус: {2} +Розмір: {3} +Операційна система: {4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +Продовжити відновлення ОС? + Надання відновлення ОС + Ініціалізація відновлення ОС... + Впровадження корисного навантаження відновлення ОС... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + Реєстрація меню завантаження відновлення ОС... + Відновлення ОС вимкнено. + Ініціалізація відновлення ОС пропускається в режимі відновлення. + Забезпечено відновлення ОС. + Забезпечено відновлення ОС (симуляція). Відновлення Foundry Розгорнути Windows знову diff --git a/src/Foundry.Deploy/Strings/zh-CN/Resources.resx b/src/Foundry.Deploy/Strings/zh-CN/Resources.resx index 23e33ae1..2c859c33 100644 --- a/src/Foundry.Deploy/Strings/zh-CN/Resources.resx +++ b/src/Foundry.Deploy/Strings/zh-CN/Resources.resx @@ -509,25 +509,25 @@ 交互式 Autopilot 注册助手已暂存(模拟)。 - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + 确认操作系统恢复 + 操作系统恢复将保留 EFI、MSR 和恢复分区,然后替换所选磁盘上的 Windows 分区。 -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +磁盘:{0} +型号:{1} +巴士:{2} +尺寸:{3} +操作系统:{4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +继续操作系统恢复吗? + 配置操作系统恢复 + 配置操作系统恢复... + 正在注入操作系统恢复负载... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + 正在注册操作系统恢复启动菜单... + 操作系统恢复已禁用。 + 在恢复模式下会跳过操作系统恢复配置。 + 已配置操作系统恢复。 + 已配置操作系统恢复(模拟)。 Foundry 恢复 重新部署 Windows diff --git a/src/Foundry.Deploy/Strings/zh-TW/Resources.resx b/src/Foundry.Deploy/Strings/zh-TW/Resources.resx index af6dccb5..da370bb8 100644 --- a/src/Foundry.Deploy/Strings/zh-TW/Resources.resx +++ b/src/Foundry.Deploy/Strings/zh-TW/Resources.resx @@ -509,25 +509,25 @@ 互動式 Autopilot 註冊助理已暫存 (模擬)。 - Confirm OS Recovery - OS Recovery will preserve EFI, MSR, and Recovery partitions, then replace the Windows partition on the selected disk. + 確認作業系統恢復 + 作業系統復原將保留 EFI、MSR 和復原分割區,然後取代所選磁碟上的 Windows 分割區。 -Disk: {0} -Model: {1} -Bus: {2} -Size: {3} -Operating system: {4} +磁碟:{0} +型號:{1} +巴士:{2} +尺寸:{3} +作業系統:{4} -Continue with OS Recovery? - Provision OS Recovery - Provisioning OS Recovery... - Injecting OS Recovery payload... +繼續作業系統恢復嗎? + 配置作業系統恢復 + 配置作業系統恢復... + 正在註入作業系統恢復負載... Committing WinRE changes... - Registering OS Recovery boot menu... - OS Recovery is disabled. - OS Recovery provisioning is skipped in recovery mode. - OS Recovery provisioned. - OS Recovery provisioned (simulation). + 正在註冊作業系統恢復啟動選單... + 作業系統恢復已停用。 + 在復原模式下會跳過作業系統恢復配置。 + 已配置作業系統恢復。 + 已配置作業系統恢復(模擬)。 Foundry 復原 重新部署 Windows diff --git a/src/Foundry/Strings/ar-SA/Resources.resw b/src/Foundry/Strings/ar-SA/Resources.resw index a3a20c2f..4daa010f 100644 --- a/src/Foundry/Strings/ar-SA/Resources.resw +++ b/src/Foundry/Strings/ar-SA/Resources.resw @@ -2122,60 +2122,60 @@ راجع الجاهزية واختر إخراج ISO أو USB ثم ابدأ إنشاء الوسائط. - Configure OS Recovery availability. + تكوين توفر استرداد نظام التشغيل. - OS Recovery + استرداد نظام التشغيل - OS Recovery + استرداد نظام التشغيل - OS Recovery + استرداد نظام التشغيل - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + قم بتمكين نقطة دخول WinRE لـ Foundry Recovery وراجع كيفية وصول المشغلين إليها. - Enable OS Recovery + تمكين استرداد نظام التشغيل - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + قم بإضافة Foundry Recovery كإجراء استرداد اختياري ضمن بيئة الإصلاح في Windows. - Enable OS Recovery + تمكين استرداد نظام التشغيل - Why it exists + لماذا هو موجود - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + يوفر استرداد نظام التشغيل لفرق الدعم مسار WinRE مألوفًا لبدء تشغيل Foundry Recovery عندما يتعذر على Windows التمهيد بشكل طبيعي أو يحتاج إلى إعادة نشر تعتمد على الإصلاح. - When to use it + متى تستخدمه - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + استخدمه لسير عمل إصلاح الأعطال حيث يبدأ المشغل من بيئة الاسترداد في Windows بدلاً من تشغيل وسائط نشر منفصلة. - WinRE path + مسار WinRE - Troubleshoot > Advanced options > Foundry Recovery + استكشاف الأخطاء وإصلاحها > الخيارات المتقدمة > Foundry Recovery - Limitations + القيود - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + تتحكم هذه الصفحة فقط فيما إذا كانت نقطة إدخال الاسترداد مطلوبة أم لا. ولا يقوم بتخصيص نص القائمة، أو تكوين الاسترداد غير المراقب، أو توفير مصادقة الشبكة، أو تسجيل Autopilot، أو إدخال بيانات OA3، أو تخزين الأسرار. - Recovery flow + تدفق الانتعاش - The placeholder image shows the intended WinRE navigation path and can be replaced later. + تُظهر صورة العنصر النائب مسار التنقل WinRE المقصود ويمكن استبدالها لاحقًا. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + مسار التنقل في WinRE: استكشاف الأخطاء وإصلاحها، الخيارات المتقدمة، استرداد Foundry. diff --git a/src/Foundry/Strings/bg-BG/Resources.resw b/src/Foundry/Strings/bg-BG/Resources.resw index fb1290d3..b97cc6fe 100644 --- a/src/Foundry/Strings/bg-BG/Resources.resw +++ b/src/Foundry/Strings/bg-BG/Resources.resw @@ -2122,60 +2122,60 @@ Прегледайте готовността, изберете ISO или USB изход и стартирайте създаването на носител. - Configure OS Recovery availability. + Конфигурирайте наличността на възстановяване на ОС. - OS Recovery + Възстановяване на ОС - OS Recovery + Възстановяване на ОС - OS Recovery + Възстановяване на ОС - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Активирайте входна точка на WinRE за Foundry Recovery и прегледайте как операторите достигат до нея. - Enable OS Recovery + Активиране на възстановяването на ОС - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Добавете Foundry Recovery като незадължително действие за възстановяване в Windows Recovery Environment. - Enable OS Recovery + Активиране на възстановяването на ОС - Why it exists + Защо съществува - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + Възстановяването на ОС предоставя на екипите за поддръжка познат път на WinRE за стартиране на Foundry Recovery, когато Windows не може да стартира нормално или се нуждае от повторно разполагане, насочено към ремонт. - When to use it + Кога да го използвате - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Използвайте го за работни потоци за коригиране на прекъсвания, при които оператор стартира от Windows Recovery Environment, вместо да зарежда отделен носител за разполагане. - WinRE path + WinRE път - Troubleshoot > Advanced options > Foundry Recovery + Отстраняване на неизправности > Разширени опции > Foundry Recovery - Limitations + Ограничения - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Тази страница контролира само дали се изисква входна точка за възстановяване. Той не персонализира текста на менюто, не конфигурира автоматично възстановяване, осигурява удостоверяване на мрежата, регистрира Autopilot, инжектира OA3 данни или съхранява тайни. - Recovery flow + Поток за възстановяване - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Изображението на контейнера показва предвидения път за навигация на WinRE и може да бъде заменено по-късно. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Път за навигация на WinRE: Отстраняване на неизправности, Разширени опции, Foundry Recovery. diff --git a/src/Foundry/Strings/cs-CZ/Resources.resw b/src/Foundry/Strings/cs-CZ/Resources.resw index 90083d28..222c1e5d 100644 --- a/src/Foundry/Strings/cs-CZ/Resources.resw +++ b/src/Foundry/Strings/cs-CZ/Resources.resw @@ -2122,60 +2122,60 @@ Zkontrolujte připravenost, vyberte výstup ISO nebo USB a spusťte vytvoření média. - Configure OS Recovery availability. + Nakonfigurujte dostupnost obnovení OS. - OS Recovery + Obnova OS - OS Recovery + Obnova OS - OS Recovery + Obnova OS - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Povolte vstupní bod WinRE pro Foundry Recovery a zkontrolujte, jak jej operátoři dosáhnou. - Enable OS Recovery + Povolit obnovení OS - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Přidejte Foundry Recovery jako volitelnou akci obnovení do prostředí Windows Recovery Environment. - Enable OS Recovery + Povolit obnovení OS - Why it exists + Proč existuje - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery poskytuje týmům podpory známou cestu WinRE pro spuštění Foundry Recovery, když Windows nelze normálně spustit nebo potřebuje přemístění řízené opravou. - When to use it + Kdy ji použít - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Použijte jej pro pracovní postupy s opravou přerušení, kdy operátor začíná z prostředí Windows Recovery Environment namísto zavádění samostatného média pro nasazení. - WinRE path + Cesta WinRE - Troubleshoot > Advanced options > Foundry Recovery + Odstraňování problémů > Pokročilé možnosti > Foundry Recovery - Limitations + Omezení - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Tato stránka řídí pouze to, zda je požadován vstupní bod obnovení. Nepřizpůsobuje text nabídky, nekonfiguruje bezobslužnou obnovu, zajišťuje síťové ověřování, nezařazuje Autopilot, vkládá data OA3 ani neukládá tajemství. - Recovery flow + Tok obnovy - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Zástupný obrázek ukazuje zamýšlenou navigační cestu WinRE a může být později nahrazen. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Navigační cesta WinRE: Odstraňování problémů, Pokročilé možnosti, Foundry Recovery. diff --git a/src/Foundry/Strings/da-DK/Resources.resw b/src/Foundry/Strings/da-DK/Resources.resw index bfd820bd..13d3789b 100644 --- a/src/Foundry/Strings/da-DK/Resources.resw +++ b/src/Foundry/Strings/da-DK/Resources.resw @@ -2122,60 +2122,60 @@ Gennemgå klarheden, vælg ISO- eller USB-output, og start medieoprettelsen. - Configure OS Recovery availability. + Konfigurer OS Gendannelse tilgængelighed. - OS Recovery + OS Gendannelse - OS Recovery + OS Gendannelse - OS Recovery + OS Gendannelse - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Aktiver et WinRE-indgangspunkt for Foundry Recovery og se, hvordan operatører når det. - Enable OS Recovery + Aktiver OS-gendannelse - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Tilføj Foundry Recovery som en valgfri gendannelseshandling under Windows Recovery Environment. - Enable OS Recovery + Aktiver OS-gendannelse - Why it exists + Hvorfor det eksisterer - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery giver supportteams en velkendt WinRE-sti til at starte Foundry Recovery, når Windows ikke kan starte normalt eller har brug for reparationsdrevet omplacering. - When to use it + Hvornår skal man bruge det - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Brug det til break-fix-arbejdsgange, hvor en operatør starter fra Windows Recovery Environment i stedet for at starte separate installationsmedier. - WinRE path + WinRE-sti - Troubleshoot > Advanced options > Foundry Recovery + Fejlfinding > Avancerede indstillinger > Foundry Recovery - Limitations + Begrænsninger - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Denne side kontrollerer kun, om gendannelsesindgangspunktet anmodes om. Den tilpasser ikke menutekst, konfigurerer uovervåget gendannelse, leverer netværksgodkendelse, tilmelder Autopilot, injicerer OA3-data eller gemmer hemmeligheder. - Recovery flow + Genopretningsflow - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Pladsholderbilledet viser den tilsigtede WinRE-navigationssti og kan erstattes senere. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE navigationssti: Fejlfinding, Avancerede indstillinger, Foundry Recovery. diff --git a/src/Foundry/Strings/de-DE/Resources.resw b/src/Foundry/Strings/de-DE/Resources.resw index 07a71d39..27befe93 100644 --- a/src/Foundry/Strings/de-DE/Resources.resw +++ b/src/Foundry/Strings/de-DE/Resources.resw @@ -2122,60 +2122,60 @@ Prüfen Sie die Bereitschaft, wählen Sie eine ISO- oder USB-Ausgabe aus, und starten Sie die Medienerstellung. - Configure OS Recovery availability. + Konfigurieren Sie die Verfügbarkeit der Betriebssystemwiederherstellung. - OS Recovery + Betriebssystemwiederherstellung - OS Recovery + Betriebssystemwiederherstellung - OS Recovery + Betriebssystemwiederherstellung - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Aktivieren Sie einen WinRE-Einstiegspunkt für Foundry Recovery und überprüfen Sie, wie Bediener ihn erreichen. - Enable OS Recovery + Aktivieren Sie die Betriebssystemwiederherstellung - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Fügen Sie Foundry Recovery als optionale Wiederherstellungsaktion unter der Windows-Wiederherstellungsumgebung hinzu. - Enable OS Recovery + Aktivieren Sie die Betriebssystemwiederherstellung - Why it exists + Warum es existiert - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery bietet Supportteams einen vertrauten WinRE-Pfad zum Starten von Foundry Recovery, wenn Windows nicht normal booten kann oder eine reparaturgesteuerte Neubereitstellung benötigt. - When to use it + Wann sollte man es verwenden? - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Verwenden Sie es für Break-Fix-Workflows, bei denen ein Bediener aus der Windows-Wiederherstellungsumgebung startet, anstatt separate Bereitstellungsmedien zu starten. - WinRE path + WinRE-Pfad - Troubleshoot > Advanced options > Foundry Recovery + Fehlerbehebung > Erweiterte Optionen > Foundry Recovery - Limitations + Einschränkungen - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Diese Seite steuert nur, ob der Wiederherstellungseinstiegspunkt angefordert wird. Es passt keinen Menütext an, konfiguriert keine unbeaufsichtigte Wiederherstellung, stellt keine Netzwerkauthentifizierung bereit, registriert keinen Autopilot, fügt keine OA3-Daten ein und speichert keine Geheimnisse. - Recovery flow + Erholungsfluss - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Das Platzhalterbild zeigt den vorgesehenen WinRE-Navigationspfad und kann später ersetzt werden. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE-Navigationspfad: Fehlerbehebung, Erweiterte Optionen, Foundry Recovery. diff --git a/src/Foundry/Strings/el-GR/Resources.resw b/src/Foundry/Strings/el-GR/Resources.resw index be6a8cd7..89ae28b4 100644 --- a/src/Foundry/Strings/el-GR/Resources.resw +++ b/src/Foundry/Strings/el-GR/Resources.resw @@ -2122,60 +2122,60 @@ Ελέγξτε την ετοιμότητα, επιλέξτε έξοδο ISO ή USB και ξεκινήστε τη δημιουργία μέσου. - Configure OS Recovery availability. + Διαμόρφωση διαθεσιμότητας OS Recovery. - OS Recovery + Ανάκτηση λειτουργικού συστήματος - OS Recovery + Ανάκτηση λειτουργικού συστήματος - OS Recovery + Ανάκτηση λειτουργικού συστήματος - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Ενεργοποιήστε ένα σημείο εισόδου WinRE για το Foundry Recovery και ελέγξτε πώς το προσεγγίζουν οι χειριστές. - Enable OS Recovery + Ενεργοποιήστε την ανάκτηση λειτουργικού συστήματος - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Προσθέστε το Foundry Recovery ως προαιρετική ενέργεια ανάκτησης στο Windows Recovery Environment. - Enable OS Recovery + Ενεργοποιήστε την ανάκτηση λειτουργικού συστήματος - Why it exists + Γιατί υπάρχει - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + Το OS Recovery παρέχει στις ομάδες υποστήριξης μια οικεία διαδρομή WinRE για την εκκίνηση του Foundry Recovery όταν τα Windows δεν μπορούν να εκκινήσουν κανονικά ή χρειάζονται αναδιάταξη βάσει επισκευής. - When to use it + Πότε να το χρησιμοποιήσετε - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Χρησιμοποιήστε το για ροές εργασιών επιδιόρθωσης διακοπής, όπου ένας χειριστής ξεκινά από το περιβάλλον αποκατάστασης των Windows αντί να εκκινεί ξεχωριστά μέσα ανάπτυξης. - WinRE path + Διαδρομή WinRE - Troubleshoot > Advanced options > Foundry Recovery + Αντιμετώπιση προβλημάτων > Προηγμένες επιλογές > Foundry Recovery - Limitations + Περιορισμοί - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Αυτή η σελίδα ελέγχει μόνο εάν ζητείται το σημείο εισόδου ανάκτησης. Δεν προσαρμόζει το κείμενο μενού, δεν διαμορφώνει την ανάκτηση χωρίς παρακολούθηση, δεν παρέχει έλεγχο ταυτότητας δικτύου, εγγράφει τον Autopilot, δεν εισάγει δεδομένα OA3 ή αποθηκεύει μυστικά. - Recovery flow + Ροή ανάκτησης - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Η εικόνα κράτησης θέσης δείχνει την προβλεπόμενη διαδρομή πλοήγησης WinRE και μπορεί να αντικατασταθεί αργότερα. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Διαδρομή πλοήγησης WinRE: Αντιμετώπιση προβλημάτων, Προηγμένες επιλογές, Ανάκτηση Foundry. diff --git a/src/Foundry/Strings/es-ES/Resources.resw b/src/Foundry/Strings/es-ES/Resources.resw index 10578a13..5359666e 100644 --- a/src/Foundry/Strings/es-ES/Resources.resw +++ b/src/Foundry/Strings/es-ES/Resources.resw @@ -2122,60 +2122,60 @@ Revise la preparación, elija una salida ISO o USB e inicie la creación del medio. - Configure OS Recovery availability. + Configure la disponibilidad de recuperación del sistema operativo. - OS Recovery + Recuperación del sistema operativo - OS Recovery + Recuperación del sistema operativo - OS Recovery + Recuperación del sistema operativo - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Habilite un punto de entrada de WinRE para Foundry Recovery y revise cómo los operadores llegan a él. - Enable OS Recovery + Habilitar la recuperación del sistema operativo - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Agregue Foundry Recovery como una acción de recuperación opcional en el entorno de recuperación de Windows. - Enable OS Recovery + Habilitar la recuperación del sistema operativo - Why it exists + Por que existe - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery brinda a los equipos de soporte una ruta familiar de WinRE para iniciar Foundry Recovery cuando Windows no puede iniciarse normalmente o necesita una reimplementación impulsada por reparación. - When to use it + Cuando usarlo - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Úselo para flujos de trabajo de reparación de averías en los que un operador inicia desde el entorno de recuperación de Windows en lugar de iniciar medios de implementación separados. - WinRE path + Ruta de WinRE - Troubleshoot > Advanced options > Foundry Recovery + Solucionar problemas > Opciones avanzadas > Recuperación de Foundry - Limitations + Limitaciones - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Esta página solo controla si se solicita el punto de entrada de recuperación. No personaliza el texto del menú, no configura la recuperación desatendida, no proporciona autenticación de red, no registra el Autopilot, no inyecta datos OA3 ni almacena secretos. - Recovery flow + Flujo de recuperación - The placeholder image shows the intended WinRE navigation path and can be replaced later. + La imagen del marcador de posición muestra la ruta de navegación prevista de WinRE y se puede reemplazar más tarde. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Ruta de navegación de WinRE: solución de problemas, opciones avanzadas, recuperación de Foundry. diff --git a/src/Foundry/Strings/es-MX/Resources.resw b/src/Foundry/Strings/es-MX/Resources.resw index cd9e9733..a2c039ef 100644 --- a/src/Foundry/Strings/es-MX/Resources.resw +++ b/src/Foundry/Strings/es-MX/Resources.resw @@ -2122,60 +2122,60 @@ Revise la preparación, elija una salida ISO o USB e inicie la creación del medio. - Configure OS Recovery availability. + Configure la disponibilidad de recuperación del sistema operativo. - OS Recovery + Recuperación del sistema operativo - OS Recovery + Recuperación del sistema operativo - OS Recovery + Recuperación del sistema operativo - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Habilite un punto de entrada de WinRE para Foundry Recovery y revise cómo los operadores llegan a él. - Enable OS Recovery + Habilitar la recuperación del sistema operativo - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Agregue Foundry Recovery como una acción de recuperación opcional en el entorno de recuperación de Windows. - Enable OS Recovery + Habilitar la recuperación del sistema operativo - Why it exists + Por que existe - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery brinda a los equipos de soporte una ruta familiar de WinRE para iniciar Foundry Recovery cuando Windows no puede iniciarse normalmente o necesita una reimplementación impulsada por reparación. - When to use it + Cuando usarlo - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Úselo para flujos de trabajo de reparación de averías en los que un operador inicia desde el entorno de recuperación de Windows en lugar de iniciar medios de implementación separados. - WinRE path + Ruta de WinRE - Troubleshoot > Advanced options > Foundry Recovery + Solucionar problemas > Opciones avanzadas > Recuperación de Foundry - Limitations + Limitaciones - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Esta página solo controla si se solicita el punto de entrada de recuperación. No personaliza el texto del menú, no configura la recuperación desatendida, no proporciona autenticación de red, no registra el Autopilot, no inyecta datos OA3 ni almacena secretos. - Recovery flow + Flujo de recuperación - The placeholder image shows the intended WinRE navigation path and can be replaced later. + La imagen del marcador de posición muestra la ruta de navegación prevista de WinRE y se puede reemplazar más tarde. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Ruta de navegación de WinRE: solución de problemas, opciones avanzadas, recuperación de Foundry. diff --git a/src/Foundry/Strings/et-EE/Resources.resw b/src/Foundry/Strings/et-EE/Resources.resw index 8898c0f0..814485ee 100644 --- a/src/Foundry/Strings/et-EE/Resources.resw +++ b/src/Foundry/Strings/et-EE/Resources.resw @@ -2122,60 +2122,60 @@ Kontrollige valmisolekut, valige ISO või USB väljund ja alustage meediumi loomist. - Configure OS Recovery availability. + OS-i taastamise kättesaadavuse seadistamine. - OS Recovery + OS-i taastamine - OS Recovery + OS-i taastamine - OS Recovery + OS-i taastamine - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Lubage Foundry Recovery jaoks WinRE sisenemispunkt ja vaadake, kuidas operaatorid selleni jõuavad. - Enable OS Recovery + Luba OS-i taastamine - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Lisage Foundry Recovery valikulise taastetoiminguna Windowsi taastekeskkonna alla. - Enable OS Recovery + Luba OS-i taastamine - Why it exists + Miks see eksisteerib - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS-i taastamine annab tugimeeskondadele tuttava WinRE-tee Foundry Recovery käivitamiseks, kui Windows ei saa normaalselt käivitada või vajab remondipõhist ümberpaigutamist. - When to use it + Millal seda kasutada - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Kasutage seda katkestuste parandamise töövoogude jaoks, kus operaator alustab Windowsi taastekeskkonnast, selle asemel et käivitada eraldi juurutuskandjat. - WinRE path + WinRE tee - Troubleshoot > Advanced options > Foundry Recovery + Veaotsing > Täpsemad valikud > Foundry Recovery - Limitations + Piirangud - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + See leht juhib ainult seda, kas taastamise sisestuspunkti taotletakse. See ei kohanda menüüteksti, konfigureeri järelevalveta taastamist, võrgu autentimist, Autopilot registreerimist, OA3-andmete sisestamist ega saladuste salvestamist. - Recovery flow + Taastumise voog - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Kohatäite pilt näitab kavandatud WinRE navigatsiooniteed ja selle saab hiljem asendada. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE navigeerimistee: tõrkeotsing, täpsemad valikud, Foundry Recovery. diff --git a/src/Foundry/Strings/fi-FI/Resources.resw b/src/Foundry/Strings/fi-FI/Resources.resw index 4c85bce1..2aff869f 100644 --- a/src/Foundry/Strings/fi-FI/Resources.resw +++ b/src/Foundry/Strings/fi-FI/Resources.resw @@ -2122,60 +2122,60 @@ Tarkista valmius, valitse ISO- tai USB-tuloste ja käynnistä median luonti. - Configure OS Recovery availability. + Määritä käyttöjärjestelmän palautuksen saatavuus. - OS Recovery + Käyttöjärjestelmän palautus - OS Recovery + Käyttöjärjestelmän palautus - OS Recovery + Käyttöjärjestelmän palautus - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Ota WinRE-tulopiste käyttöön Foundry Recoveryssa ja tarkista, kuinka operaattorit saavuttavat sen. - Enable OS Recovery + Ota käyttöjärjestelmän palautus käyttöön - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Lisää Foundry Recovery valinnaiseksi palautustoiminnoksi Windowsin palautusympäristöön. - Enable OS Recovery + Ota käyttöjärjestelmän palautus käyttöön - Why it exists + Miksi se on olemassa - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery tarjoaa tukiryhmille tutun WinRE-polun Foundry Recovery -ohjelman käynnistämiseen, kun Windows ei voi käynnistyä normaalisti tai tarvitsee korjaukseen perustuvan uudelleenasennuksen. - When to use it + Milloin sitä käytetään - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Käytä sitä häiriönkorjaustyönkuluissa, joissa operaattori aloittaa Windowsin palautusympäristöstä erillisen käyttöönottotietovälineen käynnistämisen sijaan. - WinRE path + WinRE polku - Troubleshoot > Advanced options > Foundry Recovery + Vianmääritys > Lisäasetukset > Foundry Recovery - Limitations + Rajoitukset - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Tämä sivu hallitsee vain sitä, pyydetäänkö palautuksen aloituspistettä. Se ei mukauta valikon tekstiä, määritä valvomatonta palautusta, tarjoa verkon todennusta, rekisteröi Autopilot, lisää OA3-tietoja tai tallenna salaisuuksia. - Recovery flow + Palautusvirtaus - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Paikkamerkkikuvassa näkyy aiottu WinRE-navigointipolku, ja se voidaan korvata myöhemmin. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE-navigointipolku: Vianmääritys, Lisäasetukset, Foundry Recovery. diff --git a/src/Foundry/Strings/fr-CA/Resources.resw b/src/Foundry/Strings/fr-CA/Resources.resw index 75557438..d15b6d7b 100644 --- a/src/Foundry/Strings/fr-CA/Resources.resw +++ b/src/Foundry/Strings/fr-CA/Resources.resw @@ -2122,60 +2122,60 @@ Vérifiez l’état, choisissez une sortie ISO ou USB, puis lancez la création du support. - Configure OS Recovery availability. + Configurer la disponibilité de la récupération du SE. - OS Recovery + Récupération du SE - OS Recovery + Récupération du SE - OS Recovery + Récupération du SE - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Activer un point d’entrée WinRE pour Foundry Recovery et vérifier comment les opérateurs y accèdent. - Enable OS Recovery + Activer la récupération du SE - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Ajoute Foundry Recovery comme action de récupération facultative dans Windows Recovery Environment. - Enable OS Recovery + Activer la récupération du SE - Why it exists + Pourquoi cela existe - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + La récupération du SE donne aux équipes de soutien un chemin WinRE familier pour lancer Foundry Recovery lorsque Windows ne peut pas démarrer normalement ou nécessite un redéploiement de réparation. - When to use it + Quand l’utiliser - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Utilisez-la pour les scénarios de dépannage où un opérateur démarre depuis Windows Recovery Environment au lieu de démarrer un support de déploiement séparé. - WinRE path + Chemin WinRE - Troubleshoot > Advanced options > Foundry Recovery + Dépannage > Options avancées > Foundry Recovery - Limitations + Limites - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Cette page contrôle uniquement si le point d’entrée de récupération est demandé. Elle ne personnalise pas le texte du menu, ne configure pas la récupération sans assistance, ne provisionne pas l’authentification réseau, n’inscrit pas Autopilot, n’injecte pas de données OA3 et ne stocke pas de secrets. - Recovery flow + Flux de récupération - The placeholder image shows the intended WinRE navigation path and can be replaced later. + L’image placeholder montre le chemin de navigation WinRE prévu et pourra être remplacée plus tard. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Chemin de navigation WinRE : Dépannage, Options avancées, Foundry Recovery. diff --git a/src/Foundry/Strings/fr-FR/Resources.resw b/src/Foundry/Strings/fr-FR/Resources.resw index 66ad06b0..9d0373e6 100644 --- a/src/Foundry/Strings/fr-FR/Resources.resw +++ b/src/Foundry/Strings/fr-FR/Resources.resw @@ -2122,60 +2122,60 @@ Vérifiez la disponibilité, choisissez une sortie ISO ou USB, puis démarrez la création du média. - Configure OS Recovery availability. + Configurer la disponibilité de la récupération de l’OS. - OS Recovery + Récupération de l’OS - OS Recovery + Récupération de l’OS - OS Recovery + Récupération de l’OS - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Activer un point d’entrée WinRE pour Foundry Recovery et vérifier comment les opérateurs y accèdent. - Enable OS Recovery + Activer la récupération de l’OS - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Ajoute Foundry Recovery comme action de récupération facultative dans Windows Recovery Environment. - Enable OS Recovery + Activer la récupération de l’OS - Why it exists + Pourquoi cela existe - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + La récupération de l’OS donne aux équipes de support un chemin WinRE familier pour lancer Foundry Recovery lorsque Windows ne peut pas démarrer normalement ou nécessite un redéploiement de réparation. - When to use it + Quand l’utiliser - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Utilisez-la pour les scénarios de dépannage où un opérateur démarre depuis Windows Recovery Environment au lieu de démarrer un support de déploiement séparé. - WinRE path + Chemin WinRE - Troubleshoot > Advanced options > Foundry Recovery + Dépannage > Options avancées > Foundry Recovery - Limitations + Limites - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Cette page contrôle uniquement si le point d’entrée de récupération est demandé. Elle ne personnalise pas le texte du menu, ne configure pas la récupération sans assistance, ne provisionne pas l’authentification réseau, n’inscrit pas Autopilot, n’injecte pas de données OA3 et ne stocke pas de secrets. - Recovery flow + Flux de récupération - The placeholder image shows the intended WinRE navigation path and can be replaced later. + L’image placeholder montre le chemin de navigation WinRE prévu et pourra être remplacée plus tard. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Chemin de navigation WinRE : Dépannage, Options avancées, Foundry Recovery. diff --git a/src/Foundry/Strings/he-IL/Resources.resw b/src/Foundry/Strings/he-IL/Resources.resw index 2816a554..bc62a1f9 100644 --- a/src/Foundry/Strings/he-IL/Resources.resw +++ b/src/Foundry/Strings/he-IL/Resources.resw @@ -2122,60 +2122,60 @@ בדוק מוכנות, בחר פלט ISO או USB והתחל ביצירת המדיה. - Configure OS Recovery availability. + הגדר את זמינות שחזור מערכת ההפעלה. - OS Recovery + שחזור מערכת הפעלה - OS Recovery + שחזור מערכת הפעלה - OS Recovery + שחזור מערכת הפעלה - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + אפשר נקודת כניסה של WinRE עבור Foundry Recovery וסקור כיצד מפעילים מגיעים אליה. - Enable OS Recovery + אפשר שחזור מערכת הפעלה - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + הוסף Foundry Recovery כפעולת שחזור אופציונלית תחת סביבת השחזור של Windows. - Enable OS Recovery + אפשר שחזור מערכת הפעלה - Why it exists + למה זה קיים - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery נותן לצוותי תמיכה נתיב WinRE מוכר להפעלת Foundry Recovery כאשר Windows אינו יכול לאתחל כרגיל או זקוק לפריסה מחדש מונעת תיקון. - When to use it + מתי להשתמש בו - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + השתמש בו עבור זרימות עבודה של תיקון הפסקות שבהן מפעיל מתחיל מסביבת השחזור של Windows במקום לאתחל מדיית פריסה נפרדת. - WinRE path + נתיב WinRE - Troubleshoot > Advanced options > Foundry Recovery + פתרון בעיות > אפשרויות מתקדמות > Foundry Recovery - Limitations + מגבלות - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + דף זה שולט רק אם נקודת הכניסה לשחזור מתבקשת. הוא אינו מבצע התאמה אישית של טקסט התפריט, מגדיר שחזור ללא השגחה, מתן אימות רשת, רושם Autopilot, מזרים נתוני OA3 או מאחסן סודות. - Recovery flow + זרימת התאוששות - The placeholder image shows the intended WinRE navigation path and can be replaced later. + תמונת מציין המיקום מציגה את נתיב הניווט המיועד של WinRE וניתן להחליפה מאוחר יותר. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + נתיב ניווט WinRE: פתרון בעיות, אפשרויות מתקדמות, Foundry Recovery. diff --git a/src/Foundry/Strings/hr-HR/Resources.resw b/src/Foundry/Strings/hr-HR/Resources.resw index 3e93f273..87e88adf 100644 --- a/src/Foundry/Strings/hr-HR/Resources.resw +++ b/src/Foundry/Strings/hr-HR/Resources.resw @@ -2122,60 +2122,60 @@ Pregledajte spremnost, odaberite ISO ili USB izlaz i pokrenite stvaranje medija. - Configure OS Recovery availability. + Konfigurirajte dostupnost OS Recovery. - OS Recovery + Oporavak OS-a - OS Recovery + Oporavak OS-a - OS Recovery + Oporavak OS-a - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Omogućite WinRE ulaznu točku za Foundry Recovery i pregledajte kako operateri dolaze do nje. - Enable OS Recovery + Omogući oporavak OS-a - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Dodajte Foundry Recovery kao opcionalnu radnju oporavka u Windows Recovery Environment. - Enable OS Recovery + Omogući oporavak OS-a - Why it exists + Zašto postoji - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery daje timovima za podršku poznati WinRE put za pokretanje programa Foundry Recovery kada se Windows ne može normalno pokrenuti ili mu je potrebna ponovna implementacija na temelju popravka. - When to use it + Kada ga koristiti - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Koristite ga za radne tijekove popravljanja prijeloma gdje operater počinje iz okruženja za oporavak sustava Windows umjesto pokretanja zasebnog medija za implementaciju. - WinRE path + WinRE put - Troubleshoot > Advanced options > Foundry Recovery + Rješavanje problema > Napredne opcije > Foundry Recovery - Limitations + Ograničenja - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Ova stranica kontrolira samo je li zatražena ulazna točka oporavka. Ne prilagođava tekst izbornika, konfigurira oporavak bez nadzora, pruža mrežnu provjeru autentičnosti, upisuje Autopilot, ubacuje OA3 podatke ili pohranjuje tajne. - Recovery flow + Tijek oporavka - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Slika rezerviranog mjesta prikazuje predviđenu navigacijsku putanju WinRE i može se zamijeniti kasnije. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE navigacijski put: Rješavanje problema, Napredne opcije, Foundry Recovery. diff --git a/src/Foundry/Strings/hu-HU/Resources.resw b/src/Foundry/Strings/hu-HU/Resources.resw index 6a1112d8..63a6029b 100644 --- a/src/Foundry/Strings/hu-HU/Resources.resw +++ b/src/Foundry/Strings/hu-HU/Resources.resw @@ -2122,60 +2122,60 @@ Tekintse át a készenlétet, válasszon ISO vagy USB kimenetet, majd indítsa el az adathordozó létrehozását. - Configure OS Recovery availability. + Konfigurálja az operációs rendszer helyreállításának elérhetőségét. - OS Recovery + Operációs rendszer helyreállítása - OS Recovery + Operációs rendszer helyreállítása - OS Recovery + Operációs rendszer helyreállítása - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Engedélyezze a WinRE belépési pontot a Foundry Recovery számára, és tekintse át, hogyan érik el az üzemeltetők. - Enable OS Recovery + Operációs rendszer helyreállításának engedélyezése - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + A Foundry Recovery hozzáadása opcionális helyreállítási műveletként a Windows helyreállítási környezetben. - Enable OS Recovery + Operációs rendszer helyreállításának engedélyezése - Why it exists + Miért létezik? - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + Az operációs rendszer helyreállítása ismerős WinRE elérési utat biztosít a támogató csapatok számára a Foundry Recovery elindításához, amikor a Windows nem tud normál módon elindulni, vagy javítási célú átcsoportosításra van szükség. - When to use it + Mikor kell használni? - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Használja olyan hibaelhárítási munkafolyamatokhoz, ahol az operátor a Windows helyreállítási környezetből indul, ahelyett, hogy külön telepítési adathordozót indítana el. - WinRE path + WinRE elérési út - Troubleshoot > Advanced options > Foundry Recovery + Hibaelhárítás > Speciális beállítások > Foundry Recovery - Limitations + Korlátozások - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Ez az oldal csak azt vezérli, hogy a helyreállítási belépési pont szükséges-e. Nem szabja testre a menü szövegét, nem konfigurálja a felügyelet nélküli helyreállítást, nem biztosítja a hálózati hitelesítést, nem regisztrálja az Autopilot, nem adja be az OA3-adatokat, és nem tárol titkokat. - Recovery flow + Visszanyerési áram - The placeholder image shows the intended WinRE navigation path and can be replaced later. + A helyőrző kép a kívánt WinRE navigációs útvonalat mutatja, és később cserélhető. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE navigációs útvonal: Hibaelhárítás, Speciális beállítások, Foundry Recovery. diff --git a/src/Foundry/Strings/it-IT/Resources.resw b/src/Foundry/Strings/it-IT/Resources.resw index 67addca0..5949ab45 100644 --- a/src/Foundry/Strings/it-IT/Resources.resw +++ b/src/Foundry/Strings/it-IT/Resources.resw @@ -2122,60 +2122,60 @@ Verifica la preparazione, scegli un output ISO o USB e avvia la creazione del supporto. - Configure OS Recovery availability. + Configurare la disponibilità del ripristino del sistema operativo. - OS Recovery + Ripristino OS - OS Recovery + Ripristino OS - OS Recovery + Ripristino OS - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Abilita un punto di ingresso WinRE per Foundry Recovery ed esamina il modo in cui gli operatori lo raggiungono. - Enable OS Recovery + Abilita ripristino del sistema operativo - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Aggiungere Foundry Recovery come azione di ripristino opzionale in Ambiente ripristino Windows. - Enable OS Recovery + Abilita ripristino del sistema operativo - Why it exists + Perché esiste - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery offre ai team di supporto un percorso WinRE familiare per avviare Foundry Recovery quando Windows non può avviarsi normalmente o necessita di una ridistribuzione basata sulla riparazione. - When to use it + Quando usarlo - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Utilizzarlo per flussi di lavoro break-fix in cui un operatore inizia da Ambiente ripristino Windows invece di avviare supporti di distribuzione separati. - WinRE path + Percorso WinRE - Troubleshoot > Advanced options > Foundry Recovery + Risoluzione dei problemi > Opzioni avanzate > Foundry Recovery - Limitations + Limitazioni - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Questa pagina controlla solo se è richiesto il punto di ingresso di ripristino. Non personalizza il testo del menu, configura il ripristino automatico, esegue il provisioning dell'autenticazione di rete, registra il Autopilot, inietta dati OA3 o memorizza segreti. - Recovery flow + Flusso di recupero - The placeholder image shows the intended WinRE navigation path and can be replaced later. + L'immagine segnaposto mostra il percorso di navigazione WinRE previsto e può essere sostituita in seguito. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Percorso di navigazione WinRE: risoluzione dei problemi, opzioni avanzate, Foundry Recovery. diff --git a/src/Foundry/Strings/ja-JP/Resources.resw b/src/Foundry/Strings/ja-JP/Resources.resw index c9b0a769..dd61722f 100644 --- a/src/Foundry/Strings/ja-JP/Resources.resw +++ b/src/Foundry/Strings/ja-JP/Resources.resw @@ -2122,60 +2122,60 @@ 準備状況を確認し、ISO または USB 出力を選択して、メディアの作成を開始します。 - Configure OS Recovery availability. + OSリカバリの可用性を設定します。 - OS Recovery + OSリカバリ - OS Recovery + OSリカバリ - OS Recovery + OSリカバリ - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Foundry RecoveryのWinREエントリーポイントを有効にし、オペレーターがどのように到達するかを確認します。 - Enable OS Recovery + OSリカバリを有効にする - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Windowsリカバリ環境で、オプションのリカバリアクションとしてFoundryリカバリを追加します。 - Enable OS Recovery + OSリカバリを有効にする - Why it exists + なぜ存在するのか - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OSリカバリは、Windowsが正常に起動できない場合、または修理主導の再展開が必要な場合に、サポートチームがFoundryリカバリを起動するための使い慣れたWinREパスを提供します。 - When to use it + 使用するタイミング - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + 別の展開メディアを起動する代わりに、オペレータがWindowsリカバリ環境から開始するブレークフィックスワークフローに使用します。 - WinRE path + WinREパス - Troubleshoot > Advanced options > Foundry Recovery + トラブルシューティング>高度なオプション> Foundry Recovery - Limitations + 制限事項 - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + このページでは、リカバリエントリポイントが要求されるかどうかのみを制御します。 メニューテキストをカスタマイズしたり、無人リカバリを設定したり、ネットワーク認証をプロビジョニングしたり、Autopilotを登録したり、OA 3データを挿入したり、シークレットを保存したりすることはありません。 - Recovery flow + 回復フロー - The placeholder image shows the intended WinRE navigation path and can be replaced later. + プレースホルダー画像は、意図されたWinREナビゲーションパスを示し、後で置き換えることができます。 - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinREナビゲーションパス:トラブルシューティング、高度なオプション、Foundryリカバリ。 diff --git a/src/Foundry/Strings/ko-KR/Resources.resw b/src/Foundry/Strings/ko-KR/Resources.resw index b76341b7..52b6210b 100644 --- a/src/Foundry/Strings/ko-KR/Resources.resw +++ b/src/Foundry/Strings/ko-KR/Resources.resw @@ -2122,60 +2122,87 @@ 준비 상태를 검토하고 ISO 또는 USB 출력을 선택한 다음 미디어 만들기를 시작합니다. - Configure OS Recovery availability. + @ +OS 복구 가용성을 구성합니다. +@ - OS Recovery + @ +OS 복구 +@ - OS Recovery + @ +OS 복구 - OS Recovery + @ +OS 복구 +@ - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + @ +Foundry Recovery에 대한 WinRE 진입점을 활성화하고 운영자가 도달하는 방법을 검토합니다. +@ - Enable OS Recovery + @ +OS 복구 활성화 - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + @ +Windows 복구 환경에서 선택적 복구 작업으로 Foundry Recovery를 추가합니다. +@ - Enable OS Recovery + @ +OS 복구 활성화 +@ - Why it exists + @ +숙소가 존재하는 이유 - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + @ +OS 복구는 Windows가 정상적으로 부팅되지 않거나 수리 중심의 재배포가 필요한 경우 지원 팀에게 Foundry Recovery를 시작할 수 있는 친숙한 WinRE 경로를 제공합니다. +@ - When to use it + @ +사용 시기 +@ - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + @ +운영자가 별도의 배포 미디어를 부팅하는 대신 Windows 복구 환경에서 시작하는 브레이크 픽스 워크플로우에 사용합니다. - WinRE path + @ +WinRE 경로 +@ - Troubleshoot > Advanced options > Foundry Recovery + 문제 해결 > 고급 옵션 > Foundry Recovery - Limitations + @ +제한 사항 - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + @ +이 페이지는 복구 진입점 요청 여부만 제어합니다. 메뉴 텍스트를 사용자 정의하거나, 무인 복구를 구성하거나, 네트워크 인증을 프로비저닝하거나, Autopilot을 등록하거나, OA3 데이터를 주입하거나, 비밀을 저장하지 않습니다. - Recovery flow + @ +복구 흐름 +@ - The placeholder image shows the intended WinRE navigation path and can be replaced later. + @ +자리 표시자 이미지는 의도된 WinRE 탐색 경로를 보여주며 나중에 교체할 수 있습니다. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE 탐색 경로: 문제 해결, 고급 옵션, Foundry Recovery. diff --git a/src/Foundry/Strings/lt-LT/Resources.resw b/src/Foundry/Strings/lt-LT/Resources.resw index 9b2c02b1..95065a90 100644 --- a/src/Foundry/Strings/lt-LT/Resources.resw +++ b/src/Foundry/Strings/lt-LT/Resources.resw @@ -2122,60 +2122,60 @@ Peržiūrėkite parengtį, pasirinkite ISO arba USB išvestį ir pradėkite laikmenos kūrimą. - Configure OS Recovery availability. + Konfigūruokite OS atkūrimo prieinamumą. - OS Recovery + OS atkūrimas - OS Recovery + OS atkūrimas - OS Recovery + OS atkūrimas - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Įgalinkite Foundry Recovery WinRE įėjimo tašką ir peržiūrėkite, kaip operatoriai jį pasiekia. - Enable OS Recovery + Įgalinti OS atkūrimą - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Pridėkite Foundry Recovery kaip pasirenkamą atkūrimo veiksmą „Windows“ atkūrimo aplinkoje. - Enable OS Recovery + Įgalinti OS atkūrimą - Why it exists + Kodėl taip yra - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + „OS Recovery“ suteikia palaikymo komandoms įprastą „WinRE“ kelią paleisti „Foundry Recovery“, kai „Windows“ negali normaliai paleisti arba reikia atlikti taisomąjį pakartotinį diegimą. - When to use it + Kada jį naudoti - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Naudokite jį „break-fix“ darbo eigoms, kai operatorius paleidžiamas iš „Windows“ atkūrimo aplinkos, o ne įkelia atskiras diegimo laikmenas. - WinRE path + WinRE kelias - Troubleshoot > Advanced options > Foundry Recovery + Trikčių šalinimas > Išplėstinės parinktys > Foundry Recovery - Limitations + Apribojimai - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Šis puslapis kontroliuoja tik tai, ar prašoma atkūrimo įėjimo taško. Ji nepritaiko meniu teksto, nekonfigūruoja neprižiūrimo atkūrimo, neteikia tinklo autentifikavimo, neužregistruoja Autopilot, neinjektuoja OA3 duomenų ar nesaugo paslapčių. - Recovery flow + Atkūrimo srautas - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Vietos rezervavimo ženklo paveikslėlyje rodomas numatytas WinRE naršymo kelias, kurį vėliau galima pakeisti. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE naršymo kelias: trikčių šalinimas, išplėstinės parinktys, Foundry Recovery. diff --git a/src/Foundry/Strings/lv-LV/Resources.resw b/src/Foundry/Strings/lv-LV/Resources.resw index 58c9323c..5cb66a6c 100644 --- a/src/Foundry/Strings/lv-LV/Resources.resw +++ b/src/Foundry/Strings/lv-LV/Resources.resw @@ -2122,60 +2122,60 @@ Pārskatiet gatavību, izvēlieties ISO vai USB izvadi un sāciet datu nesēja izveidi. - Configure OS Recovery availability. + Konfigurējiet OS atkopšanas pieejamību. - OS Recovery + OS atkopšana - OS Recovery + OS atkopšana - OS Recovery + OS atkopšana - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Iespējojiet WinRE ieejas punktu Foundry Recovery un pārskatiet, kā operatori to sasniedz. - Enable OS Recovery + Iespējot OS atkopšanu - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Pievieno Foundry Recovery kā neobligātu atkopšanas darbību Windows Recovery Environment vidē. - Enable OS Recovery + Iespējot OS atkopšanu - Why it exists + Kādēļ tas pastāv - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS atkopšana sniedz atbalsta komandām pazīstamu WinRE ceļu Foundry Recovery palaišanai, ja Windows nevar normāli sāknēt vai nepieciešama labošanas pārizvietošana. - When to use it + Kad to lietot - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Izmantojiet to labojumfailu darbplūsmām, kurās operators startē no Windows atkopšanas vides, nevis sāknē atsevišķu izvietošanas datu nesēju. - WinRE path + WinRE ceļš - Troubleshoot > Advanced options > Foundry Recovery + Problēmu novēršana > Papildu opcijas > Foundry Recovery - Limitations + Ierobežojumi - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Šī lapa kontrolē tikai to, vai ir pieprasīts atkopšanas ieejas punkts. Tas nepielāgo izvēlnes tekstu, nekonfigurē neuzraudzītu atkopšanu, nenodrošina tīkla autentifikāciju, nereģistrē Autopilot, neievada OA3 datus vai neglabā noslēpumus. - Recovery flow + Reģenerācijas plūsma - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Viettura attēlā ir redzams paredzētais WinRE navigācijas ceļš, un to var aizstāt vēlāk. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE navigācijas ceļš: Problēmu novēršana, Papildu opcijas, Foundry Recovery. diff --git a/src/Foundry/Strings/nb-NO/Resources.resw b/src/Foundry/Strings/nb-NO/Resources.resw index f565942a..0e244c3b 100644 --- a/src/Foundry/Strings/nb-NO/Resources.resw +++ b/src/Foundry/Strings/nb-NO/Resources.resw @@ -2122,60 +2122,60 @@ Kontroller beredskapen, velg ISO- eller USB-utdata, og start medieopprettingen. - Configure OS Recovery availability. + Konfigurer tilgjengelighet for OS-gjenoppretting. - OS Recovery + OS-gjenoppretting - OS Recovery + OS-gjenoppretting - OS Recovery + OS-gjenoppretting - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Aktiver et WinRE-inngangspunkt for Foundry Recovery og se hvordan operatører når det. - Enable OS Recovery + Aktiver OS-gjenoppretting - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Legg til Foundry Recovery som en valgfri gjenopprettingshandling under Windows Recovery Environment. - Enable OS Recovery + Aktiver OS-gjenoppretting - Why it exists + Hvorfor den eksisterer - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery gir støtteteam en kjent WinRE-bane for å starte Foundry Recovery når Windows ikke kan starte opp normalt eller trenger reparasjonsdrevet omdistribuering. - When to use it + Når du skal bruke den - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Bruk den til break-fix-arbeidsflyter der en operatør starter fra Windows-gjenopprettingsmiljøet i stedet for å starte opp separate distribusjonsmedier. - WinRE path + WinRE-bane - Troubleshoot > Advanced options > Foundry Recovery + Feilsøking > Avanserte alternativer > Foundry Recovery - Limitations + Begrensninger - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Denne siden kontrollerer bare om gjenopprettingsinngangspunktet er forespurt. Den tilpasser ikke menytekst, konfigurerer uovervåket gjenoppretting, klargjør nettverksautentisering, registrerer Autopilot, injiserer OA3-data eller lagrer hemmeligheter. - Recovery flow + Gjenopprettingsflyt - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Plassholderbildet viser den tiltenkte WinRE-navigasjonsbanen og kan erstattes senere. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE-navigasjonssti: Feilsøking, Avanserte alternativer, Foundry Recovery. diff --git a/src/Foundry/Strings/nl-NL/Resources.resw b/src/Foundry/Strings/nl-NL/Resources.resw index 85ce2696..7a251627 100644 --- a/src/Foundry/Strings/nl-NL/Resources.resw +++ b/src/Foundry/Strings/nl-NL/Resources.resw @@ -2122,60 +2122,60 @@ Controleer de gereedheid, kies ISO- of USB-uitvoer en start het maken van media. - Configure OS Recovery availability. + Beschikbaarheid van OS-herstel configureren. - OS Recovery + OS-herstel - OS Recovery + OS-herstel - OS Recovery + OS-herstel - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Schakel een WinRE-ingangspunt in voor Foundry Recovery en bekijk hoe operators dit bereiken. - Enable OS Recovery + OS-herstel inschakelen - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Voeg Foundry Recovery toe als een optionele herstelactie onder Windows Recovery Environment. - Enable OS Recovery + OS-herstel inschakelen - Why it exists + Waarom het bestaat - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery biedt ondersteuningsteams een bekend WinRE-pad voor het starten van Foundry Recovery wanneer Windows niet normaal kan opstarten of een reparatiegestuurde herschikking nodig heeft. - When to use it + Wanneer te gebruiken - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Gebruik het voor break-fix workflows waarbij een operator start vanuit Windows Recovery Environment in plaats van afzonderlijke implementatiemedia op te starten. - WinRE path + WinRE-pad - Troubleshoot > Advanced options > Foundry Recovery + Problemen oplossen > Geavanceerde opties > Foundry Recovery - Limitations + Beperkingen - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Deze pagina bepaalt alleen of het herstelinvoerpunt wordt aangevraagd. Het past de menutekst niet aan, configureert geen onbeheerd herstel, voorziet niet in netwerkverificatie, schrijft Autopilot niet in, injecteert geen OA3-gegevens en slaat geen geheimen op. - Recovery flow + Herstelstroom - The placeholder image shows the intended WinRE navigation path and can be replaced later. + De tijdelijke aanduiding toont het beoogde WinRE-navigatiepad en kan later worden vervangen. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE-navigatiepad: problemen oplossen, geavanceerde opties, Foundry Recovery. diff --git a/src/Foundry/Strings/pl-PL/Resources.resw b/src/Foundry/Strings/pl-PL/Resources.resw index 5329d788..adb61af2 100644 --- a/src/Foundry/Strings/pl-PL/Resources.resw +++ b/src/Foundry/Strings/pl-PL/Resources.resw @@ -2122,60 +2122,60 @@ Sprawdź gotowość, wybierz wyjście ISO lub USB i rozpocznij tworzenie nośnika. - Configure OS Recovery availability. + Skonfiguruj dostępność odzyskiwania systemu operacyjnego. - OS Recovery + Odzyskiwanie systemu operacyjnego - OS Recovery + Odzyskiwanie systemu operacyjnego - OS Recovery + Odzyskiwanie systemu operacyjnego - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Włącz punkt wejścia WinRE dla Foundry Recovery i sprawdź, jak operatorzy do niego docierają. - Enable OS Recovery + Włącz odzyskiwanie systemu operacyjnego - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Dodaj Foundry Recovery jako opcjonalną akcję odzyskiwania w środowisku odzyskiwania systemu Windows. - Enable OS Recovery + Włącz odzyskiwanie systemu operacyjnego - Why it exists + Dlaczego istnieje - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + System operacyjny Recovery zapewnia zespołom wsparcia znaną ścieżkę WinRE do uruchamiania Foundry Recovery, gdy system Windows nie może się normalnie uruchomić lub wymaga ponownego uruchomienia w celu naprawy. - When to use it + Kiedy go używać - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Użyj go do procedur naprawczych, w których operator zaczyna od środowiska odzyskiwania systemu Windows, zamiast uruchamiać oddzielny nośnik wdrażania. - WinRE path + Ścieżka WinRE - Troubleshoot > Advanced options > Foundry Recovery + Rozwiązywanie problemów > Opcje zaawansowane > Foundry Recovery - Limitations + Ograniczenia - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Ta strona kontroluje tylko, czy żądany jest punkt wejścia odzyskiwania. Nie dostosowuje tekstu menu, nie konfiguruje nienadzorowanego odzyskiwania, nie zapewnia uwierzytelniania sieciowego, nie rejestruje Autopilot, nie wstrzykuje danych OA3 ani nie przechowuje tajemnic. - Recovery flow + Przepływ odzysku - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Obraz zastępczy pokazuje zamierzoną ścieżkę nawigacji WinRE i może zostać zastąpiony później. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Ścieżka nawigacji WinRE: Rozwiązywanie problemów, Opcje zaawansowane, Foundry Recovery. diff --git a/src/Foundry/Strings/pt-BR/Resources.resw b/src/Foundry/Strings/pt-BR/Resources.resw index b638f2c6..e3cfc80a 100644 --- a/src/Foundry/Strings/pt-BR/Resources.resw +++ b/src/Foundry/Strings/pt-BR/Resources.resw @@ -2122,60 +2122,60 @@ Revise a prontidão, escolha uma saída ISO ou USB e inicie a criação da mídia. - Configure OS Recovery availability. + Configurar a disponibilidade da Recuperação do SO. - OS Recovery + Recuperação de SO - OS Recovery + Recuperação de SO - OS Recovery + Recuperação de SO - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Habilite um ponto de entrada WinRE para Foundry Recovery e analise como os operadores o alcançam. - Enable OS Recovery + Ativar recuperação do sistema operacional - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Adicione o Foundry Recovery como uma ação de recuperação opcional no Ambiente de Recuperação do Windows. - Enable OS Recovery + Ativar recuperação do sistema operacional - Why it exists + Por que existe - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + A recuperação do sistema operacional oferece às equipes de suporte um caminho familiar do WinRE para iniciar a recuperação do Foundry quando o Windows não consegue inicializar normalmente ou precisa de redistribuição orientada por reparo. - When to use it + Quando usar - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Use-o para fluxos de trabalho de correção de avarias em que um operador inicia a partir do Ambiente de Recuperação do Windows em vez de inicializar a mídia de implantação separada. - WinRE path + Caminho WinRE - Troubleshoot > Advanced options > Foundry Recovery + Solução de problemas > Opções avançadas > Foundry Recovery - Limitations + Limitações - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Esta página controla apenas se o ponto de entrada de recuperação é solicitado. Ele não personaliza o texto do menu, configura a recuperação autônoma, fornece autenticação de rede, registra o Autopilot, injeta dados OA3 ou armazena segredos. - Recovery flow + Fluxo de recuperação - The placeholder image shows the intended WinRE navigation path and can be replaced later. + A imagem do espaço reservado mostra o caminho de navegação do WinRE pretendido e pode ser substituída posteriormente. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Caminho de navegação do WinRE: Solução de problemas, Opções avançadas, Recuperação do Foundry. diff --git a/src/Foundry/Strings/pt-PT/Resources.resw b/src/Foundry/Strings/pt-PT/Resources.resw index d7f8ccec..a71e61ae 100644 --- a/src/Foundry/Strings/pt-PT/Resources.resw +++ b/src/Foundry/Strings/pt-PT/Resources.resw @@ -2122,60 +2122,60 @@ Reveja a preparação, escolha uma saída ISO ou USB e inicie a criação do suporte. - Configure OS Recovery availability. + Configure a disponibilidade do OS Recovery. - OS Recovery + Recuperação do sistema operacional - OS Recovery + Recuperação do sistema operacional - OS Recovery + Recuperação do sistema operacional - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Habilite um ponto de entrada WinRE para Foundry Recovery e analise como os operadores o alcançam. - Enable OS Recovery + Habilitar recuperação do sistema operacional - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Adicione Foundry Recovery como uma ação de recuperação opcional no Ambiente de Recuperação do Windows. - Enable OS Recovery + Habilitar recuperação do sistema operacional - Why it exists + Por que existe - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + O OS Recovery oferece às equipes de suporte um caminho familiar do WinRE para iniciar o Foundry Recovery quando o Windows não consegue inicializar normalmente ou precisa de uma reimplantação orientada por reparo. - When to use it + Quando usar - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Use-o para fluxos de trabalho de correção em que um operador inicia no Ambiente de Recuperação do Windows em vez de inicializar uma mídia de implantação separada. - WinRE path + Caminho WinRE - Troubleshoot > Advanced options > Foundry Recovery + Solução de problemas > Opções avançadas > Foundry Recovery - Limitations + Limitações - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Esta página controla apenas se o ponto de entrada de recuperação é solicitado. Ele não personaliza o texto do menu, configura recuperação autônoma, provisiona autenticação de rede, registra o Autopilot, injeta dados OA3 ou armazena segredos. - Recovery flow + Fluxo de recuperação - The placeholder image shows the intended WinRE navigation path and can be replaced later. + A imagem do espaço reservado mostra o caminho de navegação pretendido do WinRE e pode ser substituída posteriormente. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Caminho de navegação do WinRE: Solução de problemas, Opções avançadas, Foundry Recovery. diff --git a/src/Foundry/Strings/ro-RO/Resources.resw b/src/Foundry/Strings/ro-RO/Resources.resw index c7e33cfd..2edd561f 100644 --- a/src/Foundry/Strings/ro-RO/Resources.resw +++ b/src/Foundry/Strings/ro-RO/Resources.resw @@ -2122,60 +2122,60 @@ Revizuiți starea, alegeți ieșirea ISO sau USB și porniți crearea suportului. - Configure OS Recovery availability. + Configurați disponibilitatea recuperării OS. - OS Recovery + Recuperarea sistemului de operare - OS Recovery + Recuperarea sistemului de operare - OS Recovery + Recuperarea sistemului de operare - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Activați un punct de intrare WinRE pentru Foundry Recovery și examinați modul în care operatorii îl ajung. - Enable OS Recovery + Activați recuperarea sistemului de operare - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Adăugați Foundry Recovery ca acțiune opțională de recuperare în Windows Recovery Environment. - Enable OS Recovery + Activați recuperarea sistemului de operare - Why it exists + De ce există - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery oferă echipelor de asistență o cale WinRE familiară pentru lansarea Foundry Recovery atunci când Windows nu poate porni normal sau are nevoie de o redistribuire bazată pe reparații. - When to use it + Când să-l folosești - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Folosiți-l pentru fluxuri de lucru pentru reparații în cazul în care un operator pornește din mediul de recuperare Windows în loc să pornească medii de implementare separate. - WinRE path + Calea WinRE - Troubleshoot > Advanced options > Foundry Recovery + Depanare > Opțiuni avansate > Foundry Recovery - Limitations + Limitări - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Această pagină controlează doar dacă este solicitat punctul de intrare de recuperare. Nu personalizează textul meniului, nu configurează recuperarea nesupravegheată, nu asigură autentificarea rețelei, nu înregistrează Autopilot, nu injectează date OA3 sau nu stochează secrete. - Recovery flow + Fluxul de recuperare - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Imaginea substituent arată calea de navigare WinRE intenționată și poate fi înlocuită ulterior. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Cale de navigare WinRE: Depanare, Opțiuni avansate, Recuperare Foundry. diff --git a/src/Foundry/Strings/ru-RU/Resources.resw b/src/Foundry/Strings/ru-RU/Resources.resw index a2372323..42ad1319 100644 --- a/src/Foundry/Strings/ru-RU/Resources.resw +++ b/src/Foundry/Strings/ru-RU/Resources.resw @@ -2122,60 +2122,60 @@ Проверьте готовность, выберите вывод ISO или USB и запустите создание носителя. - Configure OS Recovery availability. + Настройте доступность восстановления ОС. - OS Recovery + Восстановление ОС - OS Recovery + Восстановление ОС - OS Recovery + Восстановление ОС - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Включите точку входа WinRE для Foundry Recovery и проверьте, как операторы достигают ее. - Enable OS Recovery + Включить восстановление ОС - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Добавьте Foundry Recovery в качестве дополнительного действия по восстановлению в среде восстановления Windows. - Enable OS Recovery + Включить восстановление ОС - Why it exists + Почему это существует - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery предоставляет группам поддержки знакомый путь WinRE для запуска Foundry Recovery, когда Windows не может нормально загрузиться или требуется повторное развертывание после восстановления. - When to use it + Когда его использовать - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Используйте его для рабочих процессов устранения неполадок, в которых оператор запускает среду восстановления Windows вместо загрузки отдельного носителя развертывания. - WinRE path + Путь WinRE - Troubleshoot > Advanced options > Foundry Recovery + Устранение неполадок > Дополнительные параметры > Foundry Recovery - Limitations + Ограничения - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Эта страница контролирует только то, запрашивается ли точка входа для восстановления. Он не настраивает текст меню, не настраивает автоматическое восстановление, не обеспечивает сетевую аутентификацию, не регистрирует Autopilot, не вводит данные OA3 и не хранит секреты. - Recovery flow + Поток восстановления - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Изображение-заполнитель показывает предполагаемый путь навигации WinRE и может быть заменено позже. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Путь навигации WinRE: Устранение неполадок, Дополнительные параметры, Foundry Recovery. diff --git a/src/Foundry/Strings/sk-SK/Resources.resw b/src/Foundry/Strings/sk-SK/Resources.resw index 76083cc6..086d36c8 100644 --- a/src/Foundry/Strings/sk-SK/Resources.resw +++ b/src/Foundry/Strings/sk-SK/Resources.resw @@ -2122,60 +2122,60 @@ Skontrolujte pripravenosť, vyberte výstup ISO alebo USB a spustite vytvorenie média. - Configure OS Recovery availability. + Nakonfigurujte dostupnosť obnovy OS. - OS Recovery + Obnova OS - OS Recovery + Obnova OS - OS Recovery + Obnova OS - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Povoľte vstupný bod WinRE pre Foundry Recovery a skontrolujte, ako ho operátori dosiahnu. - Enable OS Recovery + Povoliť obnovenie OS - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Pridajte Foundry Recovery ako voliteľnú akciu obnovenia do prostredia Windows Recovery Environment. - Enable OS Recovery + Povoliť obnovenie OS - Why it exists + Prečo existuje - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery poskytuje tímom podpory známu cestu WinRE na spustenie Foundry Recovery, keď sa systém Windows nemôže normálne zaviesť alebo potrebuje opätovné nasadenie po opravách. - When to use it + Kedy ho použiť - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Použite ho na pracovné toky opráv prerušenia, kde operátor začína z prostredia Windows Recovery Environment namiesto zavádzania samostatného média nasadenia. - WinRE path + Cesta WinRE - Troubleshoot > Advanced options > Foundry Recovery + Riešenie problémov > Rozšírené možnosti > Foundry Recovery - Limitations + Obmedzenia - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Táto stránka kontroluje iba to, či sa požaduje vstupný bod obnovy. Neprispôsobuje text ponuky, nekonfiguruje bezobslužnú obnovu, neposkytuje sieťovú autentifikáciu, nezaregistruje Autopilot, nevkladá údaje OA3 ani neukladá tajomstvá. - Recovery flow + Tok obnovy - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Zástupný obrázok zobrazuje zamýšľanú navigačnú cestu WinRE a môže byť neskôr nahradený. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Navigačná cesta WinRE: Riešenie problémov, Rozšírené možnosti, Foundry Recovery. diff --git a/src/Foundry/Strings/sl-SI/Resources.resw b/src/Foundry/Strings/sl-SI/Resources.resw index 257e92e2..6b1e428c 100644 --- a/src/Foundry/Strings/sl-SI/Resources.resw +++ b/src/Foundry/Strings/sl-SI/Resources.resw @@ -2122,60 +2122,60 @@ Preverite pripravljenost, izberite izhod ISO ali USB in začnite ustvarjanje medija. - Configure OS Recovery availability. + Konfigurirajte razpoložljivost obnovitve OS. - OS Recovery + Obnovitev OS - OS Recovery + Obnovitev OS - OS Recovery + Obnovitev OS - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Omogočite vstopno točko WinRE za Foundry Recovery in preglejte, kako jo operaterji dosežejo. - Enable OS Recovery + Omogoči obnovitev OS - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Dodajte Foundry Recovery kot neobvezno obnovitveno dejanje v obnovitvenem okolju Windows. - Enable OS Recovery + Omogoči obnovitev OS - Why it exists + Zakaj obstaja - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery nudi skupinam za podporo znano pot WinRE za zagon Foundry Recovery, ko se Windows ne more normalno zagnati ali potrebuje ponovno namestitev, ki temelji na popravilu. - When to use it + Kdaj ga uporabiti - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Uporabite ga za delovne poteke popravkov prekinitev, kjer operater začne iz obnovitvenega okolja Windows, namesto da bi zagnal ločene medije za uvajanje. - WinRE path + WinRE pot - Troubleshoot > Advanced options > Foundry Recovery + Odpravljanje težav > Napredne možnosti > Foundry Recovery - Limitations + Omejitve - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Ta stran samo nadzira, ali je zahtevana obnovitvena vstopna točka. Ne prilagaja besedila menija, konfigurira nenadzorovano obnovitev, omogoča preverjanje pristnosti omrežja, vpisuje Autopilot, vnaša podatke OA3 ali shranjuje skrivnosti. - Recovery flow + Tok okrevanja - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Slika nadomestnega znaka prikazuje predvideno navigacijsko pot WinRE in jo je mogoče pozneje zamenjati. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Navigacijska pot WinRE: Odpravljanje težav, Napredne možnosti, Foundry Recovery. diff --git a/src/Foundry/Strings/sr-Latn-RS/Resources.resw b/src/Foundry/Strings/sr-Latn-RS/Resources.resw index f7ed53a7..fb778d02 100644 --- a/src/Foundry/Strings/sr-Latn-RS/Resources.resw +++ b/src/Foundry/Strings/sr-Latn-RS/Resources.resw @@ -2122,60 +2122,60 @@ Pregledajte spremnost, izaberite ISO ili USB izlaz i pokrenite kreiranje medija. - Configure OS Recovery availability. + Konfigurišite dostupnost oporavka OS-a. - OS Recovery + Oporavak OS-a - OS Recovery + Oporavak OS-a - OS Recovery + Oporavak OS-a - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Omogućite WinRE ulaznu tačku za Foundry Recovery i pregledajte kako joj operateri pristupaju. - Enable OS Recovery + Omogući oporavak OS-a - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Dodaje Foundry Recovery kao opcionalnu radnju oporavka u Windows Recovery Environment. - Enable OS Recovery + Omogući oporavak OS-a - Why it exists + Zašto postoji - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + Oporavak OS-a daje timovima podrške poznatu WinRE putanju za pokretanje Foundry Recovery kada Windows ne može normalno da se pokrene ili zahteva ponovno postavljanje radi popravke. - When to use it + Kada ga koristiti - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Koristite ga za tokove popravke u kojima operater počinje iz Windows Recovery Environment umesto pokretanja zasebnog medija za implementaciju. - WinRE path + WinRE putanja - Troubleshoot > Advanced options > Foundry Recovery + Rešavanje problema > Napredne opcije > Foundry Recovery - Limitations + Ograničenja - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Ova stranica kontroliše samo da li se zahteva ulazna tačka oporavka. Ne prilagođava tekst menija, ne konfiguriše nenadzirani oporavak, ne obezbeđuje mrežnu autentifikaciju, ne upisuje Autopilot, ne ubacuje OA3 podatke i ne čuva tajne. - Recovery flow + Tok oporavka - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Slika rezervisanog mesta prikazuje planiranu WinRE navigacionu putanju i može se kasnije zameniti. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE navigaciona putanja: Rešavanje problema, Napredne opcije, Foundry Recovery. diff --git a/src/Foundry/Strings/sv-SE/Resources.resw b/src/Foundry/Strings/sv-SE/Resources.resw index e8bce930..9a97eae6 100644 --- a/src/Foundry/Strings/sv-SE/Resources.resw +++ b/src/Foundry/Strings/sv-SE/Resources.resw @@ -2122,60 +2122,60 @@ Granska beredskapen, välj ISO- eller USB-utdata och starta medieskapandet. - Configure OS Recovery availability. + Konfigurera OS-återställningstillgänglighet. - OS Recovery + OS-återställning - OS Recovery + OS-återställning - OS Recovery + OS-återställning - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Aktivera en WinRE-ingångspunkt för Foundry Recovery och granska hur operatörerna når den. - Enable OS Recovery + Aktivera OS-återställning - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Lägg till Foundry Recovery som en valfri återställningsåtgärd under Windows Recovery Environment. - Enable OS Recovery + Aktivera OS-återställning - Why it exists + Varför det finns - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery ger supportteam en välbekant WinRE-sökväg för att starta Foundry Recovery när Windows inte kan starta normalt eller behöver reparationsdriven omdistribuering. - When to use it + När ska man använda den - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Använd den för break-fix-arbetsflöden där en operatör startar från Windows Recovery Environment istället för att starta upp separata distributionsmedia. - WinRE path + WinRE sökväg - Troubleshoot > Advanced options > Foundry Recovery + Felsökning > Avancerade alternativ > Foundry Recovery - Limitations + Begränsningar - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Den här sidan styr bara om återställningsingångspunkten begärs. Den anpassar inte menytext, konfigurerar oövervakad återställning, tillhandahåller nätverksautentisering, registrerar Autopilot, injicerar OA3-data eller lagrar hemligheter. - Recovery flow + Återhämtningsflöde - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Platshållarbilden visar den avsedda WinRE-navigeringsvägen och kan ersättas senare. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE-navigeringsväg: Felsökning, Avancerade alternativ, Foundry Recovery. diff --git a/src/Foundry/Strings/th-TH/Resources.resw b/src/Foundry/Strings/th-TH/Resources.resw index 39c4b6be..e869f094 100644 --- a/src/Foundry/Strings/th-TH/Resources.resw +++ b/src/Foundry/Strings/th-TH/Resources.resw @@ -2122,60 +2122,60 @@ ตรวจสอบความพร้อม เลือกเอาต์พุต ISO หรือ USB แล้วเริ่มสร้างสื่อ - Configure OS Recovery availability. + กำหนดค่าความพร้อมใช้งานของการกู้คืนระบบปฏิบัติการ - OS Recovery + การกู้คืนระบบปฏิบัติการ - OS Recovery + การกู้คืนระบบปฏิบัติการ - OS Recovery + การกู้คืนระบบปฏิบัติการ - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + เปิดใช้งานจุดเข้าใช้งาน WinRE สำหรับ Foundry Recovery และตรวจสอบว่าผู้ปฏิบัติงานเข้าถึงจุดนั้นได้อย่างไร - Enable OS Recovery + เปิดใช้งานการกู้คืนระบบปฏิบัติการ - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + เพิ่ม Foundry Recovery เป็นการดำเนินการกู้คืนเสริมภายใต้ Windows Recovery Environment - Enable OS Recovery + เปิดใช้งานการกู้คืนระบบปฏิบัติการ - Why it exists + ทำไมมันถึงมีอยู่ - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery ช่วยให้ทีมสนับสนุนมีเส้นทาง WinRE ที่คุ้นเคยในการเปิดใช้ Foundry Recovery เมื่อ Windows ไม่สามารถบูตได้ตามปกติหรือจำเป็นต้องปรับใช้การซ่อมแซมใหม่ - When to use it + เมื่อใดจึงจะใช้มัน - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + ใช้สำหรับเวิร์กโฟลว์การแก้ไขที่ผู้ปฏิบัติงานเริ่มต้นจาก Windows Recovery Environment แทนที่จะบูตสื่อการปรับใช้แยกต่างหาก - WinRE path + เส้นทาง WinRE - Troubleshoot > Advanced options > Foundry Recovery + แก้ไขปัญหา > ตัวเลือกขั้นสูง > การกู้คืน Foundry - Limitations + ข้อจำกัด - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + หน้านี้ควบคุมเฉพาะว่าจะขอจุดเข้ากู้คืนหรือไม่ ไม่ได้ปรับแต่งข้อความเมนู กำหนดค่าการกู้คืนแบบอัตโนมัติ จัดเตรียมการตรวจสอบสิทธิ์เครือข่าย ลงทะเบียน Autopilot แทรกข้อมูล OA3 หรือเก็บข้อมูลลับ - Recovery flow + กระแสการกู้คืน - The placeholder image shows the intended WinRE navigation path and can be replaced later. + รูปภาพตัวยึดแสดงเส้นทางการนำทาง WinRE ที่ต้องการและสามารถแทนที่ได้ในภายหลัง - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + เส้นทางการนำทาง WinRE: การแก้ไขปัญหา, ตัวเลือกขั้นสูง, การกู้คืน Foundry diff --git a/src/Foundry/Strings/tr-TR/Resources.resw b/src/Foundry/Strings/tr-TR/Resources.resw index 55545396..b2937556 100644 --- a/src/Foundry/Strings/tr-TR/Resources.resw +++ b/src/Foundry/Strings/tr-TR/Resources.resw @@ -2122,60 +2122,60 @@ Hazırlığı gözden geçirin, ISO veya USB çıktısını seçin ve medya oluşturmayı başlatın. - Configure OS Recovery availability. + İşletim Sistemi Kurtarma kullanılabilirliğini yapılandırın. - OS Recovery + İşletim Sistemi Kurtarma - OS Recovery + İşletim Sistemi Kurtarma - OS Recovery + İşletim Sistemi Kurtarma - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Foundry Recovery için bir WinRE giriş noktasını etkinleştirin ve operatörlerin bu noktaya nasıl ulaştığını inceleyin. - Enable OS Recovery + İşletim Sistemi Kurtarmayı Etkinleştir - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Windows Kurtarma Ortamı altında Foundry Recovery'yi isteğe bağlı bir kurtarma eylemi olarak ekleyin. - Enable OS Recovery + İşletim Sistemi Kurtarmayı Etkinleştir - Why it exists + Neden var? - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + İşletim Sistemi Kurtarma, Windows'un normal şekilde önyükleme yapamadığı veya onarım odaklı yeniden dağıtıma ihtiyaç duyduğu durumlarda, destek ekiplerine Foundry Recovery'yi başlatmak için tanıdık bir WinRE yolu sağlar. - When to use it + Ne zaman kullanılmalı? - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Operatörün ayrı dağıtım ortamını önyüklemek yerine Windows Kurtarma Ortamı'ndan başlattığı arıza düzeltme iş akışları için bunu kullanın. - WinRE path + WinRE yolu - Troubleshoot > Advanced options > Foundry Recovery + Sorun giderme > Gelişmiş seçenekler > FOUNDRY_RECOVERY_product - Limitations + Sınırlamalar - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Bu sayfa yalnızca kurtarma giriş noktasının istenip istenmediğini kontrol eder. Menü metnini özelleştirmez, gözetimsiz kurtarmayı yapılandırmaz, ağ kimlik doğrulamasını sağlamaz, Autopilot kaydetmez, OA3 verilerini eklemez veya sırları saklamaz. - Recovery flow + Kurtarma akışı - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Yer tutucu görüntüsü amaçlanan WinRE gezinme yolunu gösterir ve daha sonra değiştirilebilir. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE gezinme yolu: Sorun giderme, Gelişmiş seçenekler, FOUNDRY_RECOVERY_product. diff --git a/src/Foundry/Strings/uk-UA/Resources.resw b/src/Foundry/Strings/uk-UA/Resources.resw index fd70b0f0..733c811d 100644 --- a/src/Foundry/Strings/uk-UA/Resources.resw +++ b/src/Foundry/Strings/uk-UA/Resources.resw @@ -2122,60 +2122,60 @@ Перевірте готовність, виберіть вихід ISO або USB і запустіть створення носія. - Configure OS Recovery availability. + Налаштувати доступність відновлення ОС. - OS Recovery + Відновлення ОС - OS Recovery + Відновлення ОС - OS Recovery + Відновлення ОС - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + Увімкніть точку входу WinRE для Foundry Recovery і подивіться, як оператори досягають її. - Enable OS Recovery + Увімкніть відновлення ОС - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + Додайте Foundry Recovery як додаткову дію відновлення в середовищі відновлення Windows. - Enable OS Recovery + Увімкніть відновлення ОС - Why it exists + Чому він існує - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery надає командам підтримки знайомий шлях WinRE для запуску Foundry Recovery, коли Windows не може нормально завантажитися або потребує повторного розгортання з метою відновлення. - When to use it + Коли це використовувати - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + Використовуйте його для робочих процесів усунення поломок, коли оператор починає із середовища відновлення Windows замість завантаження окремого носія для розгортання. - WinRE path + Шлях WinRE - Troubleshoot > Advanced options > Foundry Recovery + Усунення несправностей > Додаткові параметри > Foundry Recovery - Limitations + Обмеження - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + Ця сторінка лише визначає, чи запитується точка входу для відновлення. Він не налаштовує текст меню, не налаштовує автоматичне відновлення, автентифікацію мережі, не зареєстровує Autopilot, не вводить дані OA3 і не зберігає секрети. - Recovery flow + Потік відновлення - The placeholder image shows the intended WinRE navigation path and can be replaced later. + Зображення заповнювача показує передбачуваний шлях навігації WinRE, і його можна замінити пізніше. - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + Шлях навігації WinRE: усунення несправностей, додаткові параметри, Foundry Recovery. diff --git a/src/Foundry/Strings/zh-CN/Resources.resw b/src/Foundry/Strings/zh-CN/Resources.resw index b158677b..2704b167 100644 --- a/src/Foundry/Strings/zh-CN/Resources.resw +++ b/src/Foundry/Strings/zh-CN/Resources.resw @@ -2122,60 +2122,60 @@ 检查就绪情况,选择 ISO 或 USB 输出,然后开始创建媒体。 - Configure OS Recovery availability. + 配置操作系统恢复可用性。 - OS Recovery + 操作系统恢复 - OS Recovery + 操作系统恢复 - OS Recovery + 操作系统恢复 - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + 启用 Foundry Recovery 的 WinRE 入口点并查看操作员如何访问它。 - Enable OS Recovery + 启用操作系统恢复 - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + 将 Foundry 恢复添加为 Windows 恢复环境下的可选恢复操作。 - Enable OS Recovery + 启用操作系统恢复 - Why it exists + 为什么存在 - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery 为支持团队提供了熟悉的 WinRE 路径,用于在 Windows 无法正常启动或需要修复驱动的重新部署时启动 Foundry Recovery。 - When to use it + 何时使用它 - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + 将其用于故障修复工作流程,其中操作员从 Windows 恢复环境启动,而不是启动单独的部署介质。 - WinRE path + WinRE路径 - Troubleshoot > Advanced options > Foundry Recovery + 疑难解答 > 高级选项 > Foundry 恢复 - Limitations + 局限性 - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + 该页面仅控制是否请求恢复入口点。它不会自定义菜单文本、配置无人值守恢复、提供网络身份验证、注册 Autopilot、注入 OA3 数据或存储机密。 - Recovery flow + 回收流程 - The placeholder image shows the intended WinRE navigation path and can be replaced later. + 占位符图像显示预期的 WinRE 导航路径,并且可以稍后替换。 - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE 导航路径:故障排除、高级选项、Foundry 恢复。 diff --git a/src/Foundry/Strings/zh-TW/Resources.resw b/src/Foundry/Strings/zh-TW/Resources.resw index 44a89071..638e672d 100644 --- a/src/Foundry/Strings/zh-TW/Resources.resw +++ b/src/Foundry/Strings/zh-TW/Resources.resw @@ -2122,60 +2122,60 @@ 檢閱就緒狀態,選擇 ISO 或 USB 輸出,然後開始建立媒體。 - Configure OS Recovery availability. + 配置作業系統恢復可用性。 - OS Recovery + 作業系統復原 - OS Recovery + 作業系統復原 - OS Recovery + 作業系統復原 - Enable a WinRE entry point for Foundry Recovery and review how operators reach it. + 啟用 Foundry Recovery 的 WinRE 入口點並查看操作員如何存取它。 - Enable OS Recovery + 啟用作業系統復原 - Add Foundry Recovery as an optional recovery action under Windows Recovery Environment. + 將 Foundry 復原新增為 Windows 復原環境下的選用復原作業。 - Enable OS Recovery + 啟用作業系統復原 - Why it exists + 為什麼存在 - OS Recovery gives support teams a familiar WinRE path for launching Foundry Recovery when Windows cannot boot normally or needs repair-driven redeployment. + OS Recovery 為支援團隊提供了熟悉的 WinRE 路徑,可在 Windows 無法正常啟動或需要修復驅動程式的重新部署時啟動 Foundry Recovery。 - When to use it + 何時使用它 - Use it for break-fix workflows where an operator starts from Windows Recovery Environment instead of booting separate deployment media. + 將其用於故障修復工作流程,其中操作員從 Windows 復原環境啟動,而不是啟動單獨的部署媒體。 - WinRE path + WinRE路徑 - Troubleshoot > Advanced options > Foundry Recovery + 疑難排解 > 進階選項 > Foundry 恢復 - Limitations + 限制 - This page only controls whether the recovery entry point is requested. It does not customize menu text, configure unattended recovery, provision network authentication, enroll Autopilot, inject OA3 data, or store secrets. + 此頁面僅控制是否請求恢復入口點。它不會自訂選單文字、配置無人值守恢復、提供網路驗證、註冊 Autopilot、注入 OA3 資料或儲存機密。 - Recovery flow + 回收流程 - The placeholder image shows the intended WinRE navigation path and can be replaced later. + 佔位符影像顯示預期的 WinRE 導航路徑,並且可以稍後替換。 - WinRE navigation path: Troubleshoot, Advanced options, Foundry Recovery. + WinRE 導覽路徑:故障排除、進階選項、Foundry 復原。 From f74f8f896c52b4f84cc841c3780785a0fe689627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Mon, 15 Jun 2026 22:11:59 +0200 Subject: [PATCH 05/11] fix(os-recovery): remove recovery powershell dependency --- ...RecoveryPayloadProvisioningServiceTests.cs | 52 +++- .../Assets/WinRe/FoundryRecoveryLauncher.cmd | 93 ++++++- .../OsRecoveryPayloadProvisioningOptions.cs | 1 - .../OsRecoveryPayloadProvisioningService.cs | 17 -- .../HardwareProfileServiceTests.cs | 59 +++++ .../RecoveryTargetDiskResolverTests.cs | 58 +++- .../TargetDiskServiceTests.cs | 144 ++++++++++ .../WindowsDeploymentServiceTests.cs | 24 +- .../Steps/ProvisionOsRecoveryStep.cs | 1 - .../Deployment/WindowsDeploymentService.cs | 76 ++---- .../Hardware/HardwareProfileService.cs | 28 +- .../Services/Hardware/TargetDiskService.cs | 250 +++++------------- .../Runtime/RecoveryTargetDiskResolver.cs | 201 ++++++++------ .../Services/System/DiskPartOutputParser.cs | 204 ++++++++++++++ 14 files changed, 842 insertions(+), 366 deletions(-) create mode 100644 src/Foundry.Deploy.Tests/HardwareProfileServiceTests.cs create mode 100644 src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs create mode 100644 src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs diff --git a/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs b/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs index 1537eba2..d84fdfd9 100644 --- a/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs +++ b/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs @@ -14,7 +14,6 @@ public async Task ProvisionAsync_StagesRequiredFilesAndExcludesWinPeOnlyArtifact { using TempOsRecoveryWorkspace workspace = TempOsRecoveryWorkspace.Create(); string connectArchivePath = workspace.CreateArchive("connect.zip", "Foundry.Connect.exe", "connect"); - string bootstrapScript = "Write-Host 'Bootstrap'"; var service = new OsRecoveryPayloadProvisioningService( new EmbeddedLanguageRegistryService(), @@ -26,7 +25,6 @@ public async Task ProvisionAsync_StagesRequiredFilesAndExcludesWinPeOnlyArtifact MountedImagePath = workspace.MountedImagePath, WorkingDirectoryPath = workspace.WorkingDirectoryPath, Architecture = WinPeArchitecture.X64, - BootstrapScriptContent = bootstrapScript, FoundryConnectConfigurationJson = "{\"schemaVersion\":1}", DeployConfigurationJson = "{\"schemaVersion\":2}", IanaWindowsTimeZoneMapJson = "{\"zones\":[]}", @@ -51,7 +49,7 @@ public async Task ProvisionAsync_StagesRequiredFilesAndExcludesWinPeOnlyArtifact Assert.True(File.Exists(Path.Combine(toolsPath, "FoundryRecoveryLauncher.cmd"))); Assert.True(File.Exists(Path.Combine(toolsPath, "WinREConfig.xml"))); - Assert.Equal(bootstrapScript, await File.ReadAllTextAsync(Path.Combine(system32Path, "FoundryBootstrap.ps1"))); + Assert.False(File.Exists(Path.Combine(system32Path, "FoundryBootstrap.ps1"))); Assert.Equal("{\"schemaVersion\":1}", await File.ReadAllTextAsync(Path.Combine(configPath, "foundry.connect.config.json"))); Assert.Equal("{\"schemaVersion\":2}", await File.ReadAllTextAsync(Path.Combine(configPath, "foundry.deploy.config.json"))); Assert.Equal("{\"zones\":[]}", await File.ReadAllTextAsync(Path.Combine(configPath, "iana-windows-timezones.json"))); @@ -70,6 +68,49 @@ public async Task ProvisionAsync_StagesRequiredFilesAndExcludesWinPeOnlyArtifact Assert.False(Directory.Exists(Path.Combine(workspace.MountedImagePath, "Foundry", "Tools", "OA3"))); } + [Fact] + public async Task ProvisionAsync_WritesCmdOnlyRecoveryLauncher() + { + using TempOsRecoveryWorkspace workspace = TempOsRecoveryWorkspace.Create(); + string connectArchivePath = workspace.CreateArchive("connect.zip", "Foundry.Connect.exe", "connect"); + var service = new OsRecoveryPayloadProvisioningService( + new EmbeddedLanguageRegistryService(), + new WinPeRuntimePayloadProvisioningService(new FakeRuntimeProcessRunner())); + + WinPeResult result = await service.ProvisionAsync( + new OsRecoveryPayloadProvisioningOptions + { + MountedImagePath = workspace.MountedImagePath, + WorkingDirectoryPath = workspace.WorkingDirectoryPath, + Architecture = WinPeArchitecture.X64, + FoundryConnectConfigurationJson = "{}", + DeployConfigurationJson = "{}", + IanaWindowsTimeZoneMapJson = "{}", + SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, + Connect = new WinPeRuntimePayloadApplicationOptions + { + IsEnabled = true, + ArchivePath = connectArchivePath + }, + BootMenuLocalizations = CreateBootMenuLocalizations() + }, + cancellationToken: CancellationToken.None); + + Assert.True(result.IsSuccess, result.Error?.Details); + + string launcherPath = Path.Combine(workspace.MountedImagePath, "Sources", "Recovery", "Tools", "FoundryRecoveryLauncher.cmd"); + string launcher = await File.ReadAllTextAsync(launcherPath); + + Assert.Contains("FOUNDRY_DEPLOYMENT_MODE=Recovery", launcher, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Foundry.Connect.exe", launcher, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Foundry.Deploy-%RID%.zip", launcher, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Foundry.Deploy.exe", launcher, StringComparison.OrdinalIgnoreCase); + Assert.Contains("curl.exe", launcher, StringComparison.OrdinalIgnoreCase); + Assert.Contains("7za.exe", launcher, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("powershell", launcher, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("FoundryBootstrap.ps1", launcher, StringComparison.OrdinalIgnoreCase); + } + [Fact] public async Task ProvisionAsync_WritesWinReConfigXmlAsUtf8WithoutBom() { @@ -85,7 +126,6 @@ public async Task ProvisionAsync_WritesWinReConfigXmlAsUtf8WithoutBom() MountedImagePath = workspace.MountedImagePath, WorkingDirectoryPath = workspace.WorkingDirectoryPath, Architecture = WinPeArchitecture.X64, - BootstrapScriptContent = "bootstrap", FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", @@ -128,7 +168,6 @@ public async Task ProvisionAsync_ReturnsBootMenuConfigurationForEverySupportedCu MountedImagePath = workspace.MountedImagePath, WorkingDirectoryPath = workspace.WorkingDirectoryPath, Architecture = WinPeArchitecture.X64, - BootstrapScriptContent = "bootstrap", FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", @@ -172,7 +211,6 @@ public async Task ProvisionAsync_WhenBootMenuLocalizationIsMissingSupportedCultu MountedImagePath = workspace.MountedImagePath, WorkingDirectoryPath = workspace.WorkingDirectoryPath, Architecture = WinPeArchitecture.X64, - BootstrapScriptContent = "bootstrap", FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", @@ -212,7 +250,6 @@ public async Task ProvisionAsync_WhenBootMenuTextExceedsThirtyCharacters_Returns MountedImagePath = workspace.MountedImagePath, WorkingDirectoryPath = workspace.WorkingDirectoryPath, Architecture = WinPeArchitecture.X64, - BootstrapScriptContent = "bootstrap", FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", @@ -245,7 +282,6 @@ public async Task ProvisionAsync_WhenManagedPayloadExceedsBudget_ReturnsFailure( MountedImagePath = workspace.MountedImagePath, WorkingDirectoryPath = workspace.WorkingDirectoryPath, Architecture = WinPeArchitecture.X64, - BootstrapScriptContent = "bootstrap", FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", diff --git a/src/Foundry.Core/Assets/WinRe/FoundryRecoveryLauncher.cmd b/src/Foundry.Core/Assets/WinRe/FoundryRecoveryLauncher.cmd index 60f9ba05..330c543b 100644 --- a/src/Foundry.Core/Assets/WinRe/FoundryRecoveryLauncher.cmd +++ b/src/Foundry.Core/Assets/WinRe/FoundryRecoveryLauncher.cmd @@ -2,13 +2,98 @@ setlocal set "FOUNDRY_DEPLOYMENT_MODE=Recovery" -set "BOOTSTRAP_PATH=%SystemRoot%\System32\FoundryBootstrap.ps1" +set "FOUNDRY_ROOT=X:\Foundry" +set "RUNTIME_ROOT=%FOUNDRY_ROOT%\Runtime" +set "CONFIG_ROOT=%FOUNDRY_ROOT%\Config" +set "TOOLS_ROOT=%FOUNDRY_ROOT%\Tools" +set "LOG_ROOT=%FOUNDRY_ROOT%\Logs" +set "LOG_PATH=%LOG_ROOT%\FoundryRecoveryLauncher.log" -if not exist "%BOOTSTRAP_PATH%" ( - echo Missing Foundry bootstrap: %BOOTSTRAP_PATH% +if /I "%PROCESSOR_ARCHITECTURE%"=="ARM64" ( + set "RID=win-arm64" + set "SEVENZIP_RID=arm64" +) else ( + set "RID=win-x64" + set "SEVENZIP_RID=x64" +) + +set "CONNECT_ROOT=%RUNTIME_ROOT%\Foundry.Connect\%RID%" +set "CONNECT_EXE=%CONNECT_ROOT%\Foundry.Connect.exe" +set "CONNECT_CONFIG=%CONFIG_ROOT%\foundry.connect.config.json" +set "DEPLOY_ROOT=%RUNTIME_ROOT%\Foundry.Deploy\%RID%" +set "DEPLOY_ARCHIVE=%RUNTIME_ROOT%\Foundry.Deploy\Foundry.Deploy-%RID%.zip" +set "DEPLOY_EXE=%DEPLOY_ROOT%\Foundry.Deploy.exe" +set "DEPLOY_URL=https://github.com/foundry-osd/foundry/releases/latest/download/Foundry.Deploy-%RID%.zip" +set "CURL_EXE=%SystemRoot%\System32\curl.exe" +set "SEVENZIP_EXE=%TOOLS_ROOT%\7zip\%SEVENZIP_RID%\7za.exe" + +mkdir "%LOG_ROOT%" >nul 2>&1 +mkdir "%RUNTIME_ROOT%\Foundry.Deploy" >nul 2>&1 +echo [%date% %time%] Starting Foundry OS Recovery launcher.>"%LOG_PATH%" +echo [%date% %time%] Runtime identifier: %RID%.>>"%LOG_PATH%" + +if not exist "%CONNECT_EXE%" ( + echo Missing Foundry.Connect runtime: %CONNECT_EXE% + echo [%date% %time%] Missing Foundry.Connect runtime: %CONNECT_EXE%.>>"%LOG_PATH%" + exit /b 1 +) + +if not exist "%CURL_EXE%" ( + echo Missing curl.exe: %CURL_EXE% + echo [%date% %time%] Missing curl.exe: %CURL_EXE%.>>"%LOG_PATH%" + exit /b 1 +) + +if not exist "%SEVENZIP_EXE%" ( + echo Missing 7-Zip runtime: %SEVENZIP_EXE% + echo [%date% %time%] Missing 7-Zip runtime: %SEVENZIP_EXE%.>>"%LOG_PATH%" + exit /b 1 +) + +echo Starting Foundry.Connect... +echo [%date% %time%] Starting Foundry.Connect.>>"%LOG_PATH%" +if exist "%CONNECT_CONFIG%" ( + start /wait "" /d "%CONNECT_ROOT%" "%CONNECT_EXE%" --config "%CONNECT_CONFIG%" +) else ( + start /wait "" /d "%CONNECT_ROOT%" "%CONNECT_EXE%" +) + +set "CONNECT_EXIT=%ERRORLEVEL%" +echo [%date% %time%] Foundry.Connect exited with %CONNECT_EXIT%.>>"%LOG_PATH%" +if not "%CONNECT_EXIT%"=="0" ( + exit /b %CONNECT_EXIT% +) + +if not exist "%DEPLOY_EXE%" ( + echo Downloading Foundry.Deploy... + echo [%date% %time%] Downloading Foundry.Deploy from %DEPLOY_URL%.>>"%LOG_PATH%" + "%CURL_EXE%" --fail --location --show-error --output "%DEPLOY_ARCHIVE%" --url "%DEPLOY_URL%" >>"%LOG_PATH%" 2>&1 + if errorlevel 1 ( + echo Foundry.Deploy download failed. + echo [%date% %time%] Foundry.Deploy download failed.>>"%LOG_PATH%" + exit /b 1 + ) + + if exist "%DEPLOY_ROOT%" rd /s /q "%DEPLOY_ROOT%" + mkdir "%DEPLOY_ROOT%" >nul 2>&1 + echo Extracting Foundry.Deploy... + echo [%date% %time%] Extracting Foundry.Deploy.>>"%LOG_PATH%" + "%SEVENZIP_EXE%" x -y "%DEPLOY_ARCHIVE%" "-o%DEPLOY_ROOT%" >>"%LOG_PATH%" 2>&1 + if errorlevel 1 ( + echo Foundry.Deploy extraction failed. + echo [%date% %time%] Foundry.Deploy extraction failed.>>"%LOG_PATH%" + exit /b 1 + ) +) + +if not exist "%DEPLOY_EXE%" ( + echo Missing Foundry.Deploy runtime: %DEPLOY_EXE% + echo [%date% %time%] Missing Foundry.Deploy runtime after download: %DEPLOY_EXE%.>>"%LOG_PATH%" exit /b 1 ) -powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%BOOTSTRAP_PATH%" +echo Starting Foundry.Deploy... +echo [%date% %time%] Starting Foundry.Deploy.>>"%LOG_PATH%" +start "" /d "%DEPLOY_ROOT%" "%DEPLOY_EXE%" exit /b %ERRORLEVEL% diff --git a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs index 0964eaca..63d8d30d 100644 --- a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs +++ b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs @@ -7,7 +7,6 @@ public sealed record OsRecoveryPayloadProvisioningOptions public string MountedImagePath { get; init; } = string.Empty; public string WorkingDirectoryPath { get; init; } = string.Empty; public WinPeArchitecture Architecture { get; init; } = WinPeArchitecture.X64; - public string BootstrapScriptContent { get; init; } = string.Empty; public string FoundryConnectConfigurationJson { get; init; } = string.Empty; public string DeployConfigurationJson { get; init; } = string.Empty; public string IanaWindowsTimeZoneMapJson { get; init; } = string.Empty; diff --git a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs index 31861842..6ecb0fda 100644 --- a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs +++ b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs @@ -10,7 +10,6 @@ public sealed class OsRecoveryPayloadProvisioningService : IOsRecoveryPayloadPro private const string LauncherResourceName = "Foundry.Core.WinRe.FoundryRecoveryLauncher"; private const string LauncherFileName = "FoundryRecoveryLauncher.cmd"; private const string WinReConfigFileName = "WinREConfig.xml"; - private const string BootstrapFileName = "FoundryBootstrap.ps1"; private static readonly UTF8Encoding Utf8NoBom = new(false); private readonly ILanguageRegistryService _languageRegistryService; @@ -48,11 +47,9 @@ public async Task> ProvisionAsy { string mountedImagePath = Path.GetFullPath(options.MountedImagePath); string recoveryToolsPath = Path.Combine(mountedImagePath, "Sources", "Recovery", "Tools"); - string system32Path = Path.Combine(mountedImagePath, "Windows", "System32"); string foundryConfigPath = Path.Combine(mountedImagePath, "Foundry", "Config"); Directory.CreateDirectory(recoveryToolsPath); - Directory.CreateDirectory(system32Path); Directory.CreateDirectory(foundryConfigPath); string launcherContent = LoadLauncherContent(); @@ -69,11 +66,6 @@ await File.WriteAllTextAsync( winReConfigXml, Utf8NoBom, cancellationToken).ConfigureAwait(false); - await File.WriteAllTextAsync( - Path.Combine(system32Path, BootstrapFileName), - options.BootstrapScriptContent, - Utf8NoBom, - cancellationToken).ConfigureAwait(false); await File.WriteAllTextAsync( Path.Combine(foundryConfigPath, "foundry.connect.config.json"), options.FoundryConnectConfigurationJson, @@ -225,7 +217,6 @@ private static long CalculateManagedPayloadSizeBytes(string mountedImagePath, Wi [ Path.Combine(mountedImagePath, "Sources", "Recovery", "Tools", LauncherFileName), Path.Combine(mountedImagePath, "Sources", "Recovery", "Tools", WinReConfigFileName), - Path.Combine(mountedImagePath, "Windows", "System32", BootstrapFileName), Path.Combine(mountedImagePath, "Foundry", "Config", "foundry.connect.config.json"), Path.Combine(mountedImagePath, "Foundry", "Config", "foundry.deploy.config.json"), Path.Combine(mountedImagePath, "Foundry", "Config", "iana-windows-timezones.json"), @@ -324,14 +315,6 @@ private static string LoadLauncherContent() $"Value: '{options.Architecture}'."); } - if (string.IsNullOrWhiteSpace(options.BootstrapScriptContent)) - { - return new WinPeDiagnostic( - WinPeErrorCodes.ValidationFailed, - "Foundry bootstrap script content is required for OS recovery payload provisioning.", - "Set OsRecoveryPayloadProvisioningOptions.BootstrapScriptContent."); - } - if (string.IsNullOrWhiteSpace(options.FoundryConnectConfigurationJson)) { return new WinPeDiagnostic( diff --git a/src/Foundry.Deploy.Tests/HardwareProfileServiceTests.cs b/src/Foundry.Deploy.Tests/HardwareProfileServiceTests.cs new file mode 100644 index 00000000..12f32df3 --- /dev/null +++ b/src/Foundry.Deploy.Tests/HardwareProfileServiceTests.cs @@ -0,0 +1,59 @@ +using Foundry.Deploy.Models; +using Foundry.Deploy.Services.Hardware; +using Foundry.Deploy.Services.System; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Foundry.Deploy.Tests; + +public sealed class HardwareProfileServiceTests +{ + [Fact] + public async Task GetCurrentAsync_WhenPowerShellIsUnavailable_ReturnsFallbackProfileWithoutStartingPowerShell() + { + var processRunner = new RecordingProcessRunner(); + var service = new HardwareProfileService( + processRunner, + NullLogger.Instance, + _ => false); + + HardwareProfile profile = await service.GetCurrentAsync(TestContext.Current.CancellationToken); + + Assert.Equal("Unknown", profile.Manufacturer); + Assert.Empty(processRunner.Calls); + } + + private sealed class RecordingProcessRunner : IProcessRunner + { + public List Calls { get; } = []; + + public Task RunAsync( + string fileName, + string arguments, + string workingDirectory, + CancellationToken cancellationToken = default) + { + Calls.Add(fileName); + return Task.FromResult(new ProcessExecutionResult { ExitCode = 1 }); + } + + public Task RunAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + CancellationToken cancellationToken = default) + { + return RunAsync(fileName, string.Join(' ', arguments), workingDirectory, cancellationToken); + } + + public Task RunAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + Action? onOutputData, + Action? onErrorData, + CancellationToken cancellationToken = default) + { + return RunAsync(fileName, arguments, workingDirectory, cancellationToken); + } + } +} diff --git a/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs b/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs index 5d2ef487..bd75a3c4 100644 --- a/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs +++ b/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs @@ -9,43 +9,52 @@ public sealed class RecoveryTargetDiskResolverTests [Fact] public async Task ResolveAsync_WhenExactlyOneFoundryRecoveryMarkerExists_ReturnsDiskNumber() { - var processRunner = new FixedOutputProcessRunner("""{"CandidateCount":1,"DiskNumber":2}"""); - var resolver = new RecoveryTargetDiskResolver(processRunner, NullLogger.Instance); + var processRunner = new DiskPartProcessRunner(); + var resolver = new RecoveryTargetDiskResolver( + processRunner, + NullLogger.Instance, + path => path.Equals(@"Z:\Recovery\WindowsRE\FoundryOsRecovery.json", StringComparison.OrdinalIgnoreCase)); int? diskNumber = await resolver.ResolveAsync(TestContext.Current.CancellationToken); Assert.Equal(2, diskNumber); + Assert.Contains(processRunner.Calls, call => call.StartsWith("diskpart.exe ", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(processRunner.Calls, call => call.StartsWith("powershell.exe ", StringComparison.OrdinalIgnoreCase)); } - [Theory] - [InlineData("""{"CandidateCount":0}""")] - [InlineData("""{"CandidateCount":2}""")] - [InlineData("")] - public async Task ResolveAsync_WhenFoundryRecoveryMarkerIsMissingOrAmbiguous_ReturnsNull(string output) + [Fact] + public async Task ResolveAsync_WhenFoundryRecoveryMarkerIsMissing_ReturnsNull() { - var processRunner = new FixedOutputProcessRunner(output); - var resolver = new RecoveryTargetDiskResolver(processRunner, NullLogger.Instance); + var processRunner = new DiskPartProcessRunner(); + var resolver = new RecoveryTargetDiskResolver( + processRunner, + NullLogger.Instance, + _ => false); int? diskNumber = await resolver.ResolveAsync(TestContext.Current.CancellationToken); Assert.Null(diskNumber); + Assert.DoesNotContain(processRunner.Calls, call => call.StartsWith("powershell.exe ", StringComparison.OrdinalIgnoreCase)); } - private sealed class FixedOutputProcessRunner(string standardOutput) : IProcessRunner + private sealed class DiskPartProcessRunner : IProcessRunner { + public List Calls { get; } = []; + public Task RunAsync( string fileName, string arguments, string workingDirectory, CancellationToken cancellationToken = default) { + Calls.Add($"{fileName} {arguments}"); return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, FileName = fileName, Arguments = arguments, WorkingDirectory = workingDirectory, - StandardOutput = standardOutput + StandardOutput = CreateOutput(fileName, arguments) }); } @@ -68,5 +77,32 @@ public Task RunAsync( { return RunAsync(fileName, arguments, workingDirectory, cancellationToken); } + + private static string CreateOutput(string fileName, string arguments) + { + if (!string.Equals(fileName, "diskpart.exe", StringComparison.OrdinalIgnoreCase)) + { + return string.Empty; + } + + string script = File.ReadAllText(arguments.Replace("/s ", string.Empty, StringComparison.Ordinal).Trim('"')); + if (script.Contains("list partition", StringComparison.OrdinalIgnoreCase)) + { + return """ + Partition ### Type Size Offset + ------------- ---------------- ------- ------- + Partition 1 System 260 MB 1024 KB + Partition 2 Reserved 16 MB 261 MB + Partition 3 Recovery 5120 MB 277 MB + Partition 4 Primary 470 GB 5397 MB + """; + } + + return """ + Disk ### Status Size Free Dyn Gpt + -------- ------------- ------- ------- --- --- + Disk 2 Online 476 GB 0 B * + """; + } } } diff --git a/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs b/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs new file mode 100644 index 00000000..e5718abe --- /dev/null +++ b/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs @@ -0,0 +1,144 @@ +using Foundry.Deploy.Models; +using Foundry.Deploy.Services.Hardware; +using Foundry.Deploy.Services.System; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Foundry.Deploy.Tests; + +public sealed class TargetDiskServiceTests +{ + [Fact] + public async Task GetDisksAsync_UsesDiskPartAndParsesDiskInventory() + { + var processRunner = new DiskPartProcessRunner(); + var service = new TargetDiskService(processRunner, NullLogger.Instance); + + IReadOnlyList disks = await service.GetDisksAsync(TestContext.Current.CancellationToken); + + TargetDiskInfo disk = Assert.Single(disks); + Assert.Equal(0, disk.DiskNumber); + Assert.Equal("NVMe Foundry Disk", disk.FriendlyName); + Assert.Equal("NVME123", disk.SerialNumber); + Assert.Equal("NVMe", disk.BusType); + Assert.Equal("GPT", disk.PartitionStyle); + Assert.Equal(512UL * 1024UL * 1024UL * 1024UL, disk.SizeBytes); + Assert.True(disk.IsSelectable); + Assert.Contains(processRunner.Calls, call => call.StartsWith("diskpart.exe ", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(processRunner.Calls, call => call.StartsWith("powershell.exe ", StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public async Task GetDiskNumberForPathAsync_UsesDiskPartDetailVolume() + { + var processRunner = new DiskPartProcessRunner(); + var service = new TargetDiskService(processRunner, NullLogger.Instance); + + int? diskNumber = await service.GetDiskNumberForPathAsync(@"W:\Windows", TestContext.Current.CancellationToken); + + Assert.Equal(0, diskNumber); + Assert.Contains(processRunner.Calls, call => call.StartsWith("diskpart.exe ", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(processRunner.Calls, call => call.StartsWith("powershell.exe ", StringComparison.OrdinalIgnoreCase)); + } + + private sealed class DiskPartProcessRunner : IProcessRunner + { + public List Calls { get; } = []; + + public Task RunAsync( + string fileName, + string arguments, + string workingDirectory, + CancellationToken cancellationToken = default) + { + Calls.Add($"{fileName} {arguments}"); + return Task.FromResult(CreateResult(fileName, arguments)); + } + + public Task RunAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + CancellationToken cancellationToken = default) + { + return RunAsync(fileName, string.Join(' ', arguments), workingDirectory, cancellationToken); + } + + public Task RunAsync( + string fileName, + IEnumerable arguments, + string workingDirectory, + Action? onOutputData, + Action? onErrorData, + CancellationToken cancellationToken = default) + { + return RunAsync(fileName, arguments, workingDirectory, cancellationToken); + } + + private static ProcessExecutionResult CreateResult(string fileName, string arguments) + { + if (!string.Equals(fileName, "diskpart.exe", StringComparison.OrdinalIgnoreCase)) + { + return new ProcessExecutionResult { ExitCode = 0 }; + } + + string script = File.ReadAllText(arguments.Replace("/s ", string.Empty, StringComparison.Ordinal).Trim('"')); + string output = script.Contains("detail disk", StringComparison.OrdinalIgnoreCase) + ? CreateDetailDiskOutput(script) + : script.Contains("detail volume", StringComparison.OrdinalIgnoreCase) + ? """ + Disk ### Status Size Free Dyn Gpt + -------- ------------- ------- ------- --- --- + Disk 0 Online 512 GB 0 B * + """ + : """ + Disk ### Status Size Free Dyn Gpt + -------- ------------- ------- ------- --- --- + Disk 0 Online 512 GB 0 B * + Disk 1 Online 32 GB 0 B + """; + + return new ProcessExecutionResult + { + ExitCode = 0, + FileName = fileName, + Arguments = arguments, + StandardOutput = output + }; + } + + private static string CreateDetailDiskOutput(string script) + { + if (script.Contains("select disk 1", StringComparison.OrdinalIgnoreCase)) + { + return """ + USB Foundry Disk + Type : USB + Status : Online + Current Read-only State : No + Read-only : No + Boot Disk : No + Serial Number : USB123 + """; + } + + return """ + NVMe Foundry Disk + Disk ID: {00000000-0000-0000-0000-000000000000} + Type : NVMe + Status : Online + Path : 0 + Target : 0 + LUN ID : 0 + Location Path : PCIROOT(0)#PCI(0100)#NVME(P00T00L00) + Current Read-only State : No + Read-only : No + Boot Disk : No + Pagefile Disk : No + Hibernation File Disk : No + Crashdump Disk : No + Clustered Disk : No + Serial Number : NVME123 + """; + } + } +} diff --git a/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs b/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs index fd5462ba..d4463ae8 100644 --- a/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs +++ b/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs @@ -65,6 +65,8 @@ await service.PrepareTargetDiskAsync( Assert.Contains("select partition 4", scriptLines); Assert.Contains("format quick fs=ntfs label=Windows", scriptLines); Assert.Contains("assign letter=R", scriptLines); + Assert.Contains(processRunner.Calls, call => call.StartsWith("diskpart.exe ", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(processRunner.Calls, call => call.StartsWith("powershell.exe ", StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -300,6 +302,14 @@ private sealed class RecordingProcessRunner : IProcessRunner public List Calls { get; } = []; public string PowerShellOutput { get; init; } = string.Empty; + public string DiskPartOutput { get; init; } = """ + Partition ### Type Size Offset + ------------- ---------------- ------- ------- + Partition 1 System 260 MB 1024 KB + Partition 2 Reserved 16 MB 261 MB + Partition 3 Recovery 5120 MB 277 MB + Partition 4 Primary 470 GB 5397 MB + """; public Task RunAsync( string fileName, @@ -335,9 +345,17 @@ public Task RunAsync( private ProcessExecutionResult CreateResult(string fileName) { - return string.Equals(fileName, "powershell.exe", StringComparison.OrdinalIgnoreCase) - ? new ProcessExecutionResult { ExitCode = 0, StandardOutput = PowerShellOutput } - : new ProcessExecutionResult { ExitCode = 0 }; + if (string.Equals(fileName, "powershell.exe", StringComparison.OrdinalIgnoreCase)) + { + return new ProcessExecutionResult { ExitCode = 0, StandardOutput = PowerShellOutput }; + } + + if (string.Equals(fileName, "diskpart.exe", StringComparison.OrdinalIgnoreCase)) + { + return new ProcessExecutionResult { ExitCode = 0, StandardOutput = DiskPartOutput }; + } + + return new ProcessExecutionResult { ExitCode = 0 }; } } } diff --git a/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs b/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs index 87fdb940..bf05d384 100644 --- a/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs +++ b/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs @@ -123,7 +123,6 @@ await _osRecoveryPayloadProvisioningService.ProvisionAsync( MountedImagePath = mountPath, WorkingDirectoryPath = Path.Combine(workingDirectory, "Payload"), Architecture = ResolveArchitecture(context.Request.OperatingSystem.Architecture), - BootstrapScriptContent = _embeddedAssetService.GetBootstrapScriptContent(), FoundryConnectConfigurationJson = connectConfigurationJson, DeployConfigurationJson = deployConfigurationJson, IanaWindowsTimeZoneMapJson = _embeddedAssetService.GetIanaWindowsTimeZoneMapJson(), diff --git a/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs b/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs index 578fce85..1987ece1 100644 --- a/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs +++ b/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs @@ -1,6 +1,4 @@ using System.IO; -using System.Text; -using System.Text.Json; using System.Text.RegularExpressions; using System.Xml.Linq; using Foundry.Deploy.Models.Configuration; @@ -183,46 +181,17 @@ private async Task ProbeRecoveryDiskLayoutAsync( string workingDirectory, CancellationToken cancellationToken) { - string script = $$""" -$ErrorActionPreference = 'Stop' -$diskNumber = {{diskNumber}} -$recoveryGuid = '{{RecoveryPartitionGuid}}' -$efiGuid = 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b' - -$partitions = Get-Partition -DiskNumber $diskNumber | Sort-Object -Property PartitionNumber -$system = $partitions | Where-Object { $_.GptType -and ([string]$_.GptType).Trim('{}').ToLowerInvariant() -eq $efiGuid } | Select-Object -First 1 -$recovery = $partitions | Where-Object { $_.GptType -and ([string]$_.GptType).Trim('{}').ToLowerInvariant() -eq $recoveryGuid } | Select-Object -First 1 - -if ($null -eq $system) { - throw "The target disk does not contain an EFI system partition." -} - -if ($null -eq $recovery) { - throw "The target disk does not contain the active OS Recovery partition." -} - -$windows = $partitions | - Where-Object { $_.PartitionNumber -gt $recovery.PartitionNumber -and $_.Type -eq 'Basic' } | - Select-Object -First 1 - -if ($null -eq $windows) { - throw "The target disk does not contain a Windows partition after the Recovery partition." -} - -[pscustomobject]@{ - SystemPartitionNumber = [int]$system.PartitionNumber - RecoveryPartitionNumber = [int]$recovery.PartitionNumber - WindowsPartitionNumber = [int]$windows.PartitionNumber -} | ConvertTo-Json -Compress -"""; + string scriptPath = Path.Combine(workingDirectory, "diskpart-recovery-probe.txt"); + await File.WriteAllLinesAsync( + scriptPath, + [ + $"select disk {diskNumber}", + "list partition" + ], + cancellationToken).ConfigureAwait(false); - string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(script)); ProcessExecutionResult execution = await _processRunner - .RunAsync( - "powershell.exe", - $"-NoProfile -ExecutionPolicy Bypass -EncodedCommand {encoded}", - workingDirectory, - cancellationToken) + .RunAsync("diskpart.exe", $"/s \"{scriptPath}\"", workingDirectory, cancellationToken) .ConfigureAwait(false); if (!execution.IsSuccess || string.IsNullOrWhiteSpace(execution.StandardOutput)) @@ -231,21 +200,27 @@ private async Task ProbeRecoveryDiskLayoutAsync( $"Unable to validate recovery disk layout for disk {diskNumber}.{Environment.NewLine}{ToDiagnostic(execution)}"); } - RecoveryDiskLayoutProbe? probe = JsonSerializer.Deserialize( - execution.StandardOutput, - new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + IReadOnlyList partitions = DiskPartOutputParser.ParseListPartition(execution.StandardOutput); + DiskPartPartition? system = partitions.FirstOrDefault(partition => IsPartitionType(partition, "System")); + DiskPartPartition? recovery = partitions.FirstOrDefault(partition => IsPartitionType(partition, "Recovery")); + DiskPartPartition? windows = recovery is null + ? null + : partitions.FirstOrDefault(partition => + partition.Number > recovery.Number && + IsPartitionType(partition, "Primary")); - if (probe is null || - probe.SystemPartitionNumber <= 0 || - probe.RecoveryPartitionNumber <= 0 || - probe.WindowsPartitionNumber <= 0 || - probe.RecoveryPartitionNumber >= probe.WindowsPartitionNumber) + if (system is null || recovery is null || windows is null || recovery.Number >= windows.Number) { throw new InvalidOperationException( $"The target disk layout is not supported for OS Recovery. Expected EFI, MSR, Recovery, Windows on disk {diskNumber}."); } - return probe; + return new RecoveryDiskLayoutProbe + { + SystemPartitionNumber = system.Number, + RecoveryPartitionNumber = recovery.Number, + WindowsPartitionNumber = windows.Number + }; } /// @@ -1067,6 +1042,9 @@ private static char GetAvailableLetter(HashSet usedLetters, IReadOnlyList< throw new InvalidOperationException("No drive letter is available for deployment partitions."); } + private static bool IsPartitionType(DiskPartPartition partition, string expectedType) + => partition.Type.Equals(expectedType, StringComparison.OrdinalIgnoreCase); + private static void ResetWorkingDirectory(string path) { TryDeleteDirectory(path); diff --git a/src/Foundry.Deploy/Services/Hardware/HardwareProfileService.cs b/src/Foundry.Deploy/Services/Hardware/HardwareProfileService.cs index 4751da8e..5b3811c5 100644 --- a/src/Foundry.Deploy/Services/Hardware/HardwareProfileService.cs +++ b/src/Foundry.Deploy/Services/Hardware/HardwareProfileService.cs @@ -11,16 +11,33 @@ public sealed class HardwareProfileService : IHardwareProfileService { private readonly IProcessRunner _processRunner; private readonly ILogger _logger; + private readonly Func _fileExists; public HardwareProfileService(IProcessRunner processRunner, ILogger logger) + : this(processRunner, logger, File.Exists) + { + } + + internal HardwareProfileService( + IProcessRunner processRunner, + ILogger logger, + Func fileExists) { _processRunner = processRunner; _logger = logger; + _fileExists = fileExists; } public async Task GetCurrentAsync(CancellationToken cancellationToken = default) { _logger.LogInformation("Detecting current hardware profile."); + string powershellPath = ResolvePowerShellPath(); + if (!_fileExists(powershellPath)) + { + _logger.LogWarning("PowerShell was not found. Using fallback hardware profile. Path={PowerShellPath}", powershellPath); + return BuildFallbackProfile(); + } + string script = @" function ConvertTo-TrimmedString { param ( @@ -76,7 +93,7 @@ function ConvertTo-TrimmedString { string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(script)); string args = $"-NoProfile -ExecutionPolicy Bypass -EncodedCommand {encoded}"; ProcessExecutionResult execution = await _processRunner - .RunAsync("powershell.exe", args, Path.GetTempPath(), cancellationToken) + .RunAsync(powershellPath, args, Path.GetTempPath(), cancellationToken) .ConfigureAwait(false); if (!execution.IsSuccess || string.IsNullOrWhiteSpace(execution.StandardOutput)) @@ -149,6 +166,15 @@ private static HardwareProfile BuildFallbackProfile() }; } + private static string ResolvePowerShellPath() + { + return Path.Combine( + Environment.SystemDirectory, + "WindowsPowerShell", + "v1.0", + "powershell.exe"); + } + private static string ReadProperty(JsonElement root, string propertyName) { return root.TryGetProperty(propertyName, out JsonElement value) diff --git a/src/Foundry.Deploy/Services/Hardware/TargetDiskService.cs b/src/Foundry.Deploy/Services/Hardware/TargetDiskService.cs index d78f872e..0bf99634 100644 --- a/src/Foundry.Deploy/Services/Hardware/TargetDiskService.cs +++ b/src/Foundry.Deploy/Services/Hardware/TargetDiskService.cs @@ -1,6 +1,4 @@ using System.IO; -using System.Text; -using System.Text.Json; using Foundry.Deploy.Models; using Foundry.Deploy.Services.Localization; using Foundry.Deploy.Services.System; @@ -22,32 +20,9 @@ public TargetDiskService(IProcessRunner processRunner, ILogger> GetDisksAsync(CancellationToken cancellationToken = default) { _logger.LogInformation("Querying target disks."); - string script = @" -$disks = Get-Disk | Sort-Object -Property Number -$result = foreach ($disk in $disks) { - [pscustomobject]@{ - Number = [int]$disk.Number - FriendlyName = [string]$disk.FriendlyName - SerialNumber = [string]$disk.SerialNumber - BusType = [string]$disk.BusType - PartitionStyle = [string]$disk.PartitionStyle - Size = [uint64]$disk.Size - IsSystem = [bool]$disk.IsSystem - IsBoot = [bool]$disk.IsBoot - IsReadOnly = [bool]$disk.IsReadOnly - IsOffline = [bool]$disk.IsOffline - IsRemovable = [bool]$disk.IsRemovable - } -} -$result | ConvertTo-Json -Compress -"; - - string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(script)); - string args = $"-NoProfile -ExecutionPolicy Bypass -EncodedCommand {encoded}"; - - ProcessExecutionResult execution = await _processRunner - .RunAsync("powershell.exe", args, Path.GetTempPath(), cancellationToken) - .ConfigureAwait(false); + ProcessExecutionResult execution = await RunDiskPartScriptAsync( + ["list disk"], + cancellationToken).ConfigureAwait(false); if (!execution.IsSuccess || string.IsNullOrWhiteSpace(execution.StandardOutput)) { @@ -57,34 +32,20 @@ public async Task> GetDisksAsync(CancellationToken try { - using JsonDocument document = JsonDocument.Parse(execution.StandardOutput); - JsonElement root = document.RootElement; - var disks = new List(); - if (root.ValueKind == JsonValueKind.Array) + foreach (DiskPartDisk disk in DiskPartOutputParser.ParseListDisk(execution.StandardOutput)) { - foreach (JsonElement diskElement in root.EnumerateArray()) - { - TargetDiskInfo info = ParseDisk(diskElement); - if (ShouldExcludeFromTargets(info)) - { - _logger.LogInformation( - "Skipping disk {DiskNumber} from target selection because it is attached over USB. FriendlyName={FriendlyName}", - info.DiskNumber, - info.FriendlyName); - continue; - } - - disks.Add(info); - } - } - else if (root.ValueKind == JsonValueKind.Object) - { - TargetDiskInfo info = ParseDisk(root); + TargetDiskInfo info = await QueryDiskInfoAsync(disk, cancellationToken).ConfigureAwait(false); if (!ShouldExcludeFromTargets(info)) { disks.Add(info); + continue; } + + _logger.LogInformation( + "Skipping disk {DiskNumber} from target selection because it is attached over USB. FriendlyName={FriendlyName}", + info.DiskNumber, + info.FriendlyName); } TargetDiskInfo[] orderedDisks = disks @@ -123,23 +84,12 @@ public async Task> GetDisksAsync(CancellationToken return null; } - string script = $@" -$partition = Get-Partition -DriveLetter '{EscapeForSingleQuote(driveLetter)}' -ErrorAction SilentlyContinue -if ($null -eq $partition) {{ - return -}} - -[pscustomobject]@{{ - DiskNumber = [int]$partition.DiskNumber -}} | ConvertTo-Json -Compress -"; - - string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(script)); - string args = $"-NoProfile -ExecutionPolicy Bypass -EncodedCommand {encoded}"; - - ProcessExecutionResult execution = await _processRunner - .RunAsync("powershell.exe", args, Path.GetTempPath(), cancellationToken) - .ConfigureAwait(false); + ProcessExecutionResult execution = await RunDiskPartScriptAsync( + [ + $"select volume {driveLetter}", + "detail volume" + ], + cancellationToken).ConfigureAwait(false); if (!execution.IsSuccess || string.IsNullOrWhiteSpace(execution.StandardOutput)) { @@ -147,63 +97,37 @@ public async Task> GetDisksAsync(CancellationToken return null; } - try - { - using JsonDocument document = JsonDocument.Parse(execution.StandardOutput); - JsonElement rootElement = document.RootElement; - if (!rootElement.TryGetProperty("DiskNumber", out JsonElement diskNumberElement)) - { - return null; - } - - if (diskNumberElement.ValueKind == JsonValueKind.Number && diskNumberElement.TryGetInt32(out int numericValue)) - { - return numericValue; - } - - if (diskNumberElement.ValueKind == JsonValueKind.String && - int.TryParse(diskNumberElement.GetString(), out int parsedValue)) - { - return parsedValue; - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to parse disk number for path {Path}.", path); - return null; - } - - return null; + return DiskPartOutputParser.ParseDetailVolumeDiskNumber(execution.StandardOutput); } - private static TargetDiskInfo ParseDisk(JsonElement element) + private async Task QueryDiskInfoAsync(DiskPartDisk disk, CancellationToken cancellationToken) { - int diskNumber = ReadInt(element, "Number"); - string friendlyName = NormalizeValue(ReadString(element, "FriendlyName"), fallback: LocalizationText.GetString("Common.Unknown")); - string serial = NormalizeValue(ReadString(element, "SerialNumber"), fallback: LocalizationText.GetString("Common.Unknown")); - string busType = NormalizeValue(ReadString(element, "BusType"), fallback: LocalizationText.GetString("Common.Unknown")); - string partitionStyle = NormalizeValue(ReadString(element, "PartitionStyle"), fallback: LocalizationText.GetString("Common.Unknown")); - ulong sizeBytes = ReadUInt64(element, "Size"); - bool isSystem = ReadBool(element, "IsSystem"); - bool isBoot = ReadBool(element, "IsBoot"); - bool isReadOnly = ReadBool(element, "IsReadOnly"); - bool isOffline = ReadBool(element, "IsOffline"); - bool isRemovable = ReadBool(element, "IsRemovable"); + ProcessExecutionResult execution = await RunDiskPartScriptAsync( + [ + $"select disk {disk.Number}", + "detail disk" + ], + cancellationToken).ConfigureAwait(false); + + DiskPartDetailDisk detail = execution.IsSuccess && !string.IsNullOrWhiteSpace(execution.StandardOutput) + ? DiskPartOutputParser.ParseDetailDisk(disk.Number, execution.StandardOutput, disk) + : new DiskPartDetailDisk(disk.Number, string.Empty, string.Empty, string.Empty, disk.IsGpt ? "GPT" : "MBR", disk.SizeBytes, false, false, false, disk.IsOffline); - string warning = BuildSelectionWarning(isSystem, isBoot, isReadOnly, isOffline); + bool isRemovable = string.Equals(detail.BusType, "USB", StringComparison.OrdinalIgnoreCase); + string warning = BuildSelectionWarning(detail.IsSystem, detail.IsBoot, detail.IsReadOnly, detail.IsOffline); return new TargetDiskInfo { - DiskNumber = diskNumber, - FriendlyName = friendlyName, - SerialNumber = serial, - BusType = busType, - PartitionStyle = partitionStyle, - SizeBytes = sizeBytes, - IsSystem = isSystem, - IsBoot = isBoot, - IsReadOnly = isReadOnly, - IsOffline = isOffline, + DiskNumber = detail.Number, + FriendlyName = NormalizeValue(detail.FriendlyName, fallback: LocalizationText.GetString("Common.Unknown")), + SerialNumber = NormalizeValue(detail.SerialNumber, fallback: LocalizationText.GetString("Common.Unknown")), + BusType = NormalizeValue(detail.BusType, fallback: LocalizationText.GetString("Common.Unknown")), + PartitionStyle = NormalizeValue(detail.PartitionStyle, fallback: LocalizationText.GetString("Common.Unknown")), + SizeBytes = detail.SizeBytes, + IsSystem = detail.IsSystem, + IsBoot = detail.IsBoot, + IsReadOnly = detail.IsReadOnly, + IsOffline = detail.IsOffline, IsRemovable = isRemovable, IsSelectable = string.IsNullOrWhiteSpace(warning), SelectionWarning = warning @@ -238,91 +162,41 @@ private static string BuildSelectionWarning(bool isSystem, bool isBoot, bool isR private static bool ShouldExcludeFromTargets(TargetDiskInfo disk) => string.Equals(disk.BusType, "USB", StringComparison.OrdinalIgnoreCase); - private static string ReadString(JsonElement root, string propertyName) - { - if (!root.TryGetProperty(propertyName, out JsonElement property)) - { - return string.Empty; - } - - return property.ValueKind == JsonValueKind.String - ? property.GetString() ?? string.Empty - : property.ToString(); - } - - private static int ReadInt(JsonElement root, string propertyName) + private static string NormalizeValue(string value, string fallback) { - if (!root.TryGetProperty(propertyName, out JsonElement property)) - { - return -1; - } - - if (property.ValueKind == JsonValueKind.Number && property.TryGetInt32(out int value)) - { - return value; - } - - if (property.ValueKind == JsonValueKind.String && int.TryParse(property.GetString(), out int parsed)) - { - return parsed; - } - - return -1; + string normalized = value.Trim(); + return string.IsNullOrWhiteSpace(normalized) ? fallback : normalized; } - private static ulong ReadUInt64(JsonElement root, string propertyName) + private async Task RunDiskPartScriptAsync( + IReadOnlyList scriptLines, + CancellationToken cancellationToken) { - if (!root.TryGetProperty(propertyName, out JsonElement property)) - { - return 0; - } - - if (property.ValueKind == JsonValueKind.Number && property.TryGetUInt64(out ulong value)) + string scriptPath = Path.Combine(Path.GetTempPath(), $"foundry-diskpart-{Guid.NewGuid():N}.txt"); + try { - return value; + await File.WriteAllLinesAsync(scriptPath, scriptLines, cancellationToken).ConfigureAwait(false); + return await _processRunner + .RunAsync("diskpart.exe", $"/s \"{scriptPath}\"", Path.GetTempPath(), cancellationToken) + .ConfigureAwait(false); } - - if (property.ValueKind == JsonValueKind.String && ulong.TryParse(property.GetString(), out ulong parsed)) + finally { - return parsed; + TryDeleteFile(scriptPath); } - - return 0; } - private static bool ReadBool(JsonElement root, string propertyName) + private static void TryDeleteFile(string path) { - if (!root.TryGetProperty(propertyName, out JsonElement property)) - { - return false; - } - - if (property.ValueKind == JsonValueKind.True) - { - return true; - } - - if (property.ValueKind == JsonValueKind.False) + try { - return false; + if (File.Exists(path)) + { + File.Delete(path); + } } - - if (property.ValueKind == JsonValueKind.String && bool.TryParse(property.GetString(), out bool parsed)) + catch { - return parsed; } - - return false; - } - - private static string NormalizeValue(string value, string fallback) - { - string normalized = value.Trim(); - return string.IsNullOrWhiteSpace(normalized) ? fallback : normalized; - } - - private static string EscapeForSingleQuote(string value) - { - return value.Replace("'", "''", StringComparison.Ordinal); } } diff --git a/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs b/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs index db3c8e57..4acc75c0 100644 --- a/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs +++ b/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs @@ -1,6 +1,4 @@ using System.IO; -using System.Text; -using System.Text.Json; using Foundry.Deploy.Services.System; using Microsoft.Extensions.Logging; @@ -8,119 +6,156 @@ namespace Foundry.Deploy.Services.Runtime; public sealed class RecoveryTargetDiskResolver : IRecoveryTargetDiskResolver { - private const string RecoveryPartitionGuid = "de94bba4-06d1-4d40-a16a-bfd50179d6ac"; private const string RecoveryMarkerRelativePath = @"Recovery\WindowsRE\FoundryOsRecovery.json"; private readonly IProcessRunner _processRunner; private readonly ILogger _logger; + private readonly Func _fileExists; public RecoveryTargetDiskResolver(IProcessRunner processRunner, ILogger logger) + : this(processRunner, logger, File.Exists) + { + } + + internal RecoveryTargetDiskResolver( + IProcessRunner processRunner, + ILogger logger, + Func fileExists) { _processRunner = processRunner; _logger = logger; + _fileExists = fileExists; } public async Task ResolveAsync(CancellationToken cancellationToken = default) { - string script = $$""" -$ErrorActionPreference = 'Stop' -$recoveryGuid = '{{RecoveryPartitionGuid}}' -$markerRelativePath = '{{RecoveryMarkerRelativePath}}' -$assignedAccessPaths = @() -$recoveryPartitions = Get-Partition | - Where-Object { $_.GptType -and ([string]$_.GptType).Trim('{}').ToLowerInvariant() -eq $recoveryGuid } | - Sort-Object -Property DiskNumber, PartitionNumber - -try { - $candidates = @() - foreach ($partition in $recoveryPartitions) { - $accessPath = @($partition.AccessPaths | Where-Object { $_ -match '^[A-Z]:\\$' } | Select-Object -First 1)[0] - if ([string]::IsNullOrWhiteSpace($accessPath)) { - Add-PartitionAccessPath -DiskNumber $partition.DiskNumber -PartitionNumber $partition.PartitionNumber -AssignDriveLetter | Out-Null - $partition = Get-Partition -DiskNumber $partition.DiskNumber -PartitionNumber $partition.PartitionNumber - $accessPath = @($partition.AccessPaths | Where-Object { $_ -match '^[A-Z]:\\$' } | Select-Object -First 1)[0] - if (-not [string]::IsNullOrWhiteSpace($accessPath)) { - $assignedAccessPaths += [pscustomobject]@{ - DiskNumber = [int]$partition.DiskNumber - PartitionNumber = [int]$partition.PartitionNumber - AccessPath = $accessPath - } - } - } + ProcessExecutionResult listExecution = await RunDiskPartScriptAsync( + ["list disk"], + cancellationToken).ConfigureAwait(false); - if ([string]::IsNullOrWhiteSpace($accessPath)) { - continue + if (!listExecution.IsSuccess || string.IsNullOrWhiteSpace(listExecution.StandardOutput)) + { + _logger.LogWarning("Unable to resolve active OS Recovery target disk. ExitCode={ExitCode}", listExecution.ExitCode); + return null; } - $markerPath = Join-Path -Path $accessPath -ChildPath $markerRelativePath - if (Test-Path -LiteralPath $markerPath) { - $candidates += [pscustomobject]@{ - DiskNumber = [int]$partition.DiskNumber - PartitionNumber = [int]$partition.PartitionNumber - MarkerPath = $markerPath + var candidateDiskNumbers = new List(); + HashSet usedLetters = DriveInfo.GetDrives() + .Select(drive => char.ToUpperInvariant(drive.Name[0])) + .ToHashSet(); + + foreach (DiskPartDisk disk in DiskPartOutputParser.ParseListDisk(listExecution.StandardOutput)) + { + ProcessExecutionResult partitionExecution = await RunDiskPartScriptAsync( + [ + $"select disk {disk.Number}", + "list partition" + ], + cancellationToken).ConfigureAwait(false); + + if (!partitionExecution.IsSuccess || string.IsNullOrWhiteSpace(partitionExecution.StandardOutput)) + { + continue; } - } - } - if ($candidates.Count -eq 1) { - [pscustomobject]@{ - CandidateCount = [int]$candidates.Count - DiskNumber = [int]$candidates[0].DiskNumber - } | ConvertTo-Json -Compress - return - } + foreach (DiskPartPartition partition in DiskPartOutputParser.ParseListPartition(partitionExecution.StandardOutput) + .Where(partition => partition.Type.Equals("Recovery", StringComparison.OrdinalIgnoreCase))) + { + char? letter = GetAvailableTemporaryLetter(usedLetters); + if (letter is null) + { + _logger.LogWarning("No temporary drive letter is available to inspect OS Recovery partition markers."); + return null; + } - [pscustomobject]@{ - CandidateCount = [int]$candidates.Count - } | ConvertTo-Json -Compress -} -finally { - foreach ($assigned in $assignedAccessPaths) { - Remove-PartitionAccessPath -DiskNumber $assigned.DiskNumber -PartitionNumber $assigned.PartitionNumber -AccessPath $assigned.AccessPath -ErrorAction SilentlyContinue - } -} -"""; - - string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(script)); - ProcessExecutionResult execution = await _processRunner - .RunAsync( - "powershell.exe", - $"-NoProfile -ExecutionPolicy Bypass -EncodedCommand {encoded}", - Path.GetTempPath(), - cancellationToken) - .ConfigureAwait(false); - - if (!execution.IsSuccess || string.IsNullOrWhiteSpace(execution.StandardOutput)) + usedLetters.Add(letter.Value); + string markerPath = $@"{letter.Value}:\{RecoveryMarkerRelativePath}"; + bool assigned = false; + try + { + ProcessExecutionResult assignExecution = await RunDiskPartScriptAsync( + [ + $"select disk {disk.Number}", + $"select partition {partition.Number}", + $"assign letter={letter}" + ], + cancellationToken).ConfigureAwait(false); + + assigned = assignExecution.IsSuccess; + if (assigned && _fileExists(markerPath)) + { + candidateDiskNumbers.Add(disk.Number); + } + } + finally + { + if (assigned) + { + await RunDiskPartScriptAsync( + [ + $"select volume {letter}", + $"remove letter={letter} noerr" + ], + CancellationToken.None).ConfigureAwait(false); + } + + usedLetters.Remove(letter.Value); + } + } + } + + if (candidateDiskNumbers.Count != 1) { - _logger.LogWarning("Unable to resolve active OS Recovery target disk. ExitCode={ExitCode}", execution.ExitCode); + _logger.LogWarning( + "OS Recovery target disk resolver found {CandidateCount} Foundry recovery partition marker(s).", + candidateDiskNumbers.Count); return null; } + return candidateDiskNumbers[0]; + } + + private async Task RunDiskPartScriptAsync( + IReadOnlyList scriptLines, + CancellationToken cancellationToken) + { + string scriptPath = Path.Combine(Path.GetTempPath(), $"foundry-recovery-diskpart-{Guid.NewGuid():N}.txt"); try { - using JsonDocument document = JsonDocument.Parse(execution.StandardOutput); - int candidateCount = 0; - if (document.RootElement.TryGetProperty("CandidateCount", out JsonElement candidateCountElement)) - { - candidateCountElement.TryGetInt32(out candidateCount); - } + await File.WriteAllLinesAsync(scriptPath, scriptLines, cancellationToken).ConfigureAwait(false); + return await _processRunner + .RunAsync("diskpart.exe", $"/s \"{scriptPath}\"", Path.GetTempPath(), cancellationToken) + .ConfigureAwait(false); + } + finally + { + TryDeleteFile(scriptPath); + } + } - if (candidateCount != 1) + private static char? GetAvailableTemporaryLetter(HashSet usedLetters) + { + for (char letter = 'Z'; letter >= 'D'; letter--) + { + if (!usedLetters.Contains(letter)) { - _logger.LogWarning("OS Recovery target disk resolver found {CandidateCount} Foundry recovery partition marker(s).", candidateCount); - return null; + return letter; } + } + + return null; + } - if (document.RootElement.TryGetProperty("DiskNumber", out JsonElement diskNumberElement) && - diskNumberElement.TryGetInt32(out int diskNumber)) + private static void TryDeleteFile(string path) + { + try + { + if (File.Exists(path)) { - return diskNumber; + File.Delete(path); } } - catch (JsonException ex) + catch { - _logger.LogWarning(ex, "Failed to parse OS Recovery target disk resolver output."); } - - return null; } } diff --git a/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs b/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs new file mode 100644 index 00000000..ede6de98 --- /dev/null +++ b/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs @@ -0,0 +1,204 @@ +using System.Text.RegularExpressions; + +namespace Foundry.Deploy.Services.System; + +internal static class DiskPartOutputParser +{ + public static IReadOnlyList ParseListDisk(string output) + { + if (string.IsNullOrWhiteSpace(output)) + { + return []; + } + + var disks = new List(); + foreach (string line in SplitLines(output)) + { + Match match = Regex.Match(line, @"^\s*\*?\s*(?:Disk|Disque)\s+(?\d+)\s+(?\S+)", RegexOptions.IgnoreCase); + if (!match.Success) + { + continue; + } + + disks.Add(new DiskPartDisk( + int.Parse(match.Groups["number"].Value), + ParseFirstSizeBytes(line), + line.TrimEnd().EndsWith('*'), + !match.Groups["status"].Value.Equals("Online", StringComparison.OrdinalIgnoreCase))); + } + + return disks; + } + + public static IReadOnlyList ParseListPartition(string output) + { + if (string.IsNullOrWhiteSpace(output)) + { + return []; + } + + var partitions = new List(); + foreach (string line in SplitLines(output)) + { + Match match = Regex.Match( + line, + @"^\s*(?:Partition)\s+(?\d+)\s+(?\S+)", + RegexOptions.IgnoreCase); + + if (!match.Success) + { + continue; + } + + partitions.Add(new DiskPartPartition( + int.Parse(match.Groups["number"].Value), + match.Groups["type"].Value)); + } + + return partitions; + } + + public static DiskPartDetailDisk ParseDetailDisk(int diskNumber, string output, DiskPartDisk disk) + { + string friendlyName = string.Empty; + string serialNumber = string.Empty; + string busType = string.Empty; + bool isBoot = false; + bool isSystem = false; + bool isReadOnly = false; + bool isOffline = disk.IsOffline; + + foreach (string rawLine in SplitLines(output)) + { + string line = rawLine.Trim(); + if (line.Length == 0) + { + continue; + } + + if (friendlyName.Length == 0 && !line.Contains(':', StringComparison.Ordinal)) + { + friendlyName = line; + continue; + } + + if (TryReadKeyValue(line, "Serial Number", out string serial)) + { + serialNumber = serial; + continue; + } + + if (TryReadKeyValue(line, "Type", out string type)) + { + busType = type; + continue; + } + + if (TryReadKeyValue(line, "Status", out string status)) + { + isOffline = !status.Equals("Online", StringComparison.OrdinalIgnoreCase); + continue; + } + + if (TryReadKeyValue(line, "Current Read-only State", out string currentReadOnly) || + TryReadKeyValue(line, "Read-only", out currentReadOnly)) + { + isReadOnly = IsYes(currentReadOnly); + continue; + } + + if (TryReadKeyValue(line, "Boot Disk", out string bootDisk)) + { + isBoot = IsYes(bootDisk); + continue; + } + + if (TryReadKeyValue(line, "System Disk", out string systemDisk)) + { + isSystem = IsYes(systemDisk); + } + } + + return new DiskPartDetailDisk( + diskNumber, + friendlyName, + serialNumber, + busType, + disk.IsGpt ? "GPT" : "MBR", + disk.SizeBytes, + isSystem, + isBoot, + isReadOnly, + isOffline); + } + + public static int? ParseDetailVolumeDiskNumber(string output) + { + foreach (string line in SplitLines(output)) + { + Match match = Regex.Match(line, @"^\s*\*?\s*(?:Disk|Disque)\s+(?\d+)\b", RegexOptions.IgnoreCase); + if (match.Success) + { + return int.Parse(match.Groups["number"].Value); + } + } + + return null; + } + + private static IReadOnlyList SplitLines(string output) + => output.Split(["\r\n", "\n"], StringSplitOptions.None); + + private static ulong ParseFirstSizeBytes(string line) + { + Match match = Regex.Match(line, @"(?\d+)\s*(?B|KB|MB|GB|TB)\b", RegexOptions.IgnoreCase); + if (!match.Success) + { + return 0; + } + + ulong value = ulong.Parse(match.Groups["value"].Value); + return match.Groups["unit"].Value.ToUpperInvariant() switch + { + "B" => value, + "KB" => value * 1024UL, + "MB" => value * 1024UL * 1024UL, + "GB" => value * 1024UL * 1024UL * 1024UL, + "TB" => value * 1024UL * 1024UL * 1024UL * 1024UL, + _ => 0 + }; + } + + private static bool TryReadKeyValue(string line, string key, out string value) + { + value = string.Empty; + Match match = Regex.Match(line, $"^{Regex.Escape(key)}\\s*:\\s*(?.*)$", RegexOptions.IgnoreCase); + if (!match.Success) + { + return false; + } + + value = match.Groups["value"].Value.Trim(); + return true; + } + + private static bool IsYes(string value) + => value.Equals("Yes", StringComparison.OrdinalIgnoreCase) || + value.Equals("Oui", StringComparison.OrdinalIgnoreCase); +} + +internal sealed record DiskPartDisk(int Number, ulong SizeBytes, bool IsGpt, bool IsOffline); + +internal sealed record DiskPartPartition(int Number, string Type); + +internal sealed record DiskPartDetailDisk( + int Number, + string FriendlyName, + string SerialNumber, + string BusType, + string PartitionStyle, + ulong SizeBytes, + bool IsSystem, + bool IsBoot, + bool IsReadOnly, + bool IsOffline); From 859e01aeab8bcc78c7d75d9361f4b591af91904f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Mon, 15 Jun 2026 22:26:46 +0200 Subject: [PATCH 06/11] fix(os-recovery): harden diskpart parsing --- .../RecoveryTargetDiskResolverTests.cs | 61 ++++++++++++++-- .../TargetDiskServiceTests.cs | 64 +++++++++++++--- .../WindowsDeploymentServiceTests.cs | 64 +++++++++++++++- .../Deployment/WindowsDeploymentService.cs | 60 +++++++++++++-- .../Runtime/RecoveryTargetDiskResolver.cs | 32 +++++++- .../Services/System/DiskPartOutputParser.cs | 73 +++++++++++++------ 6 files changed, 309 insertions(+), 45 deletions(-) diff --git a/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs b/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs index bd75a3c4..d9c75349 100644 --- a/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs +++ b/src/Foundry.Deploy.Tests/RecoveryTargetDiskResolverTests.cs @@ -9,11 +9,11 @@ public sealed class RecoveryTargetDiskResolverTests [Fact] public async Task ResolveAsync_WhenExactlyOneFoundryRecoveryMarkerExists_ReturnsDiskNumber() { - var processRunner = new DiskPartProcessRunner(); + var processRunner = new DiskPartProcessRunner(existingLetter: null); var resolver = new RecoveryTargetDiskResolver( processRunner, NullLogger.Instance, - path => path.Equals(@"Z:\Recovery\WindowsRE\FoundryOsRecovery.json", StringComparison.OrdinalIgnoreCase)); + path => path.EndsWith(@"Recovery\WindowsRE\FoundryOsRecovery.json", StringComparison.OrdinalIgnoreCase)); int? diskNumber = await resolver.ResolveAsync(TestContext.Current.CancellationToken); @@ -25,7 +25,7 @@ public async Task ResolveAsync_WhenExactlyOneFoundryRecoveryMarkerExists_Returns [Fact] public async Task ResolveAsync_WhenFoundryRecoveryMarkerIsMissing_ReturnsNull() { - var processRunner = new DiskPartProcessRunner(); + var processRunner = new DiskPartProcessRunner(existingLetter: null); var resolver = new RecoveryTargetDiskResolver( processRunner, NullLogger.Instance, @@ -37,9 +37,26 @@ public async Task ResolveAsync_WhenFoundryRecoveryMarkerIsMissing_ReturnsNull() Assert.DoesNotContain(processRunner.Calls, call => call.StartsWith("powershell.exe ", StringComparison.OrdinalIgnoreCase)); } - private sealed class DiskPartProcessRunner : IProcessRunner + [Fact] + public async Task ResolveAsync_WhenRecoveryPartitionAlreadyHasDriveLetter_UsesExistingLetter() + { + var processRunner = new DiskPartProcessRunner(existingLetter: 'R'); + var resolver = new RecoveryTargetDiskResolver( + processRunner, + NullLogger.Instance, + path => path.Equals(@"R:\Recovery\WindowsRE\FoundryOsRecovery.json", StringComparison.OrdinalIgnoreCase)); + + int? diskNumber = await resolver.ResolveAsync(TestContext.Current.CancellationToken); + + Assert.Equal(2, diskNumber); + Assert.DoesNotContain(processRunner.ScriptContents, script => script.Contains("assign letter=", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(processRunner.Calls, call => call.StartsWith("powershell.exe ", StringComparison.OrdinalIgnoreCase)); + } + + private sealed class DiskPartProcessRunner(char? existingLetter) : IProcessRunner { public List Calls { get; } = []; + public List ScriptContents { get; } = []; public Task RunAsync( string fileName, @@ -54,7 +71,7 @@ public Task RunAsync( FileName = fileName, Arguments = arguments, WorkingDirectory = workingDirectory, - StandardOutput = CreateOutput(fileName, arguments) + StandardOutput = CreateOutput(fileName, arguments, existingLetter) }); } @@ -78,7 +95,7 @@ public Task RunAsync( return RunAsync(fileName, arguments, workingDirectory, cancellationToken); } - private static string CreateOutput(string fileName, string arguments) + private string CreateOutput(string fileName, string arguments, char? existingLetter) { if (!string.Equals(fileName, "diskpart.exe", StringComparison.OrdinalIgnoreCase)) { @@ -86,6 +103,38 @@ private static string CreateOutput(string fileName, string arguments) } string script = File.ReadAllText(arguments.Replace("/s ", string.Empty, StringComparison.Ordinal).Trim('"')); + ScriptContents.Add(script); + if (script.Contains("detail partition", StringComparison.OrdinalIgnoreCase)) + { + if (!script.Contains("select partition 3", StringComparison.OrdinalIgnoreCase)) + { + string typeGuid = script.Contains("select partition 1", StringComparison.OrdinalIgnoreCase) + ? "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" + : "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"; + + return $$""" + Partition 1 + Type : {{typeGuid}} + """; + } + + string volumeLine = existingLetter.HasValue + ? $" Volume 3 {existingLetter.Value} Recovery NTFS Partition 5120 MB Healthy Hidden" + : " Volume 3 Recovery NTFS Partition 5120 MB Healthy Hidden"; + + return $$""" + Partition 3 + Type : de94bba4-06d1-4d40-a16a-bfd50179d6ac + Hidden: Yes + Required: Yes + Attrib: 0X8000000000000001 + + Volume ### Ltr Label Fs Type Size Status Info + ---------- --- ----------- ----- ---------- ------- --------- -------- + {{volumeLine}} + """; + } + if (script.Contains("list partition", StringComparison.OrdinalIgnoreCase)) { return """ diff --git a/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs b/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs index e5718abe..b3efef8e 100644 --- a/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs +++ b/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs @@ -10,7 +10,7 @@ public sealed class TargetDiskServiceTests [Fact] public async Task GetDisksAsync_UsesDiskPartAndParsesDiskInventory() { - var processRunner = new DiskPartProcessRunner(); + var processRunner = new DiskPartProcessRunner(localizedOutput: false); var service = new TargetDiskService(processRunner, NullLogger.Instance); IReadOnlyList disks = await service.GetDisksAsync(TestContext.Current.CancellationToken); @@ -30,7 +30,7 @@ public async Task GetDisksAsync_UsesDiskPartAndParsesDiskInventory() [Fact] public async Task GetDiskNumberForPathAsync_UsesDiskPartDetailVolume() { - var processRunner = new DiskPartProcessRunner(); + var processRunner = new DiskPartProcessRunner(localizedOutput: false); var service = new TargetDiskService(processRunner, NullLogger.Instance); int? diskNumber = await service.GetDiskNumberForPathAsync(@"W:\Windows", TestContext.Current.CancellationToken); @@ -40,8 +40,26 @@ public async Task GetDiskNumberForPathAsync_UsesDiskPartDetailVolume() Assert.DoesNotContain(processRunner.Calls, call => call.StartsWith("powershell.exe ", StringComparison.OrdinalIgnoreCase)); } - private sealed class DiskPartProcessRunner : IProcessRunner + [Fact] + public async Task GetDisksAsync_WhenDiskPartOutputIsLocalized_ParsesDiskInventory() { + var processRunner = new DiskPartProcessRunner(localizedOutput: true); + var service = new TargetDiskService(processRunner, NullLogger.Instance); + + IReadOnlyList disks = await service.GetDisksAsync(TestContext.Current.CancellationToken); + + TargetDiskInfo disk = Assert.Single(disks); + Assert.Equal(0, disk.DiskNumber); + Assert.Equal("Disque Foundry NVMe", disk.FriendlyName); + Assert.Equal("NVME123", disk.SerialNumber); + Assert.Equal(512UL * 1024UL * 1024UL * 1024UL, disk.SizeBytes); + Assert.True(disk.IsSelectable); + } + + private sealed class DiskPartProcessRunner(bool localizedOutput) : IProcessRunner + { + private readonly bool _localizedOutput = localizedOutput; + public List Calls { get; } = []; public Task RunAsync( @@ -74,7 +92,7 @@ public Task RunAsync( return RunAsync(fileName, arguments, workingDirectory, cancellationToken); } - private static ProcessExecutionResult CreateResult(string fileName, string arguments) + private ProcessExecutionResult CreateResult(string fileName, string arguments) { if (!string.Equals(fileName, "diskpart.exe", StringComparison.OrdinalIgnoreCase)) { @@ -83,14 +101,21 @@ private static ProcessExecutionResult CreateResult(string fileName, string argum string script = File.ReadAllText(arguments.Replace("/s ", string.Empty, StringComparison.Ordinal).Trim('"')); string output = script.Contains("detail disk", StringComparison.OrdinalIgnoreCase) - ? CreateDetailDiskOutput(script) + ? CreateDetailDiskOutput(script, _localizedOutput) : script.Contains("detail volume", StringComparison.OrdinalIgnoreCase) ? """ Disk ### Status Size Free Dyn Gpt -------- ------------- ------- ------- --- --- Disk 0 Online 512 GB 0 B * """ - : """ + : _localizedOutput + ? """ + N° disque Statut Taille Libre Dyn GPT + --------- ------------- ------- ------- --- --- + Disque 0 En ligne 512 G octets 0 octets * + Disque 1 En ligne 32 G octets 0 octets + """ + : """ Disk ### Status Size Free Dyn Gpt -------- ------------- ------- ------- --- --- Disk 0 Online 512 GB 0 B * @@ -106,11 +131,21 @@ Disk 1 Online 32 GB 0 B }; } - private static string CreateDetailDiskOutput(string script) + private static string CreateDetailDiskOutput(string script, bool localizedOutput) { if (script.Contains("select disk 1", StringComparison.OrdinalIgnoreCase)) { - return """ + return localizedOutput + ? """ + Disque USB Foundry + Type : USB + Statut : En ligne + État de lecture seule actuel : Non + Lecture seule : Non + Disque de démarrage : Non + Numéro de série : USB123 + """ + : """ USB Foundry Disk Type : USB Status : Online @@ -121,7 +156,18 @@ USB Foundry Disk """; } - return """ + return localizedOutput + ? """ + Disque Foundry NVMe + Type : NVMe + Statut : En ligne + État de lecture seule actuel : Non + Lecture seule : Non + Disque de démarrage : Non + Disque système : Non + Numéro de série : NVME123 + """ + : """ NVMe Foundry Disk Disk ID: {00000000-0000-0000-0000-000000000000} Type : NVMe diff --git a/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs b/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs index d4463ae8..4516acee 100644 --- a/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs +++ b/src/Foundry.Deploy.Tests/WindowsDeploymentServiceTests.cs @@ -69,6 +69,39 @@ await service.PrepareTargetDiskAsync( Assert.DoesNotContain(processRunner.Calls, call => call.StartsWith("powershell.exe ", StringComparison.OrdinalIgnoreCase)); } + [Fact] + public async Task PrepareTargetDiskAsync_WhenRecoveryMode_UsesPartitionGuidDetails() + { + using var workspace = new TemporaryWorkspace(); + string workingDirectory = Path.Combine(workspace.RootPath, "Work"); + var processRunner = new RecordingProcessRunner + { + DiskPartOutput = """ + Partition ### Type Size Offset + ------------- ---------------- ------- ------- + Partition 1 Système 260 MB 1024 KB + Partition 2 Réservé 16 MB 261 MB + Partition 3 Récupération 5120 MB 277 MB + Partition 4 Principal 470 GB 5397 MB + """ + }; + var service = new WindowsDeploymentService(processRunner, NullLogger.Instance); + + await service.PrepareTargetDiskAsync( + 1, + workingDirectory, + RecoveryTargetDiskLayoutMode.RecoveryRetrySafe, + TestContext.Current.CancellationToken); + + string scriptPath = Path.Combine(workingDirectory, "diskpart-os-target.txt"); + string[] scriptLines = await File.ReadAllLinesAsync(scriptPath, TestContext.Current.CancellationToken); + + Assert.Contains("select partition 1", scriptLines); + Assert.Contains("select partition 3", scriptLines); + Assert.Contains("select partition 4", scriptLines); + Assert.Contains(processRunner.ScriptContents, script => script.Contains("detail partition", StringComparison.OrdinalIgnoreCase)); + } + [Fact] public async Task ConfigureOfflineComputerNameAsync_WhenDefaultTimeZoneIdIsProvided_WritesUnattendTimeZone() { @@ -300,6 +333,7 @@ public Task RunAsync( private sealed class RecordingProcessRunner : IProcessRunner { public List Calls { get; } = []; + public List ScriptContents { get; } = []; public string PowerShellOutput { get; init; } = string.Empty; public string DiskPartOutput { get; init; } = """ @@ -352,10 +386,38 @@ private ProcessExecutionResult CreateResult(string fileName) if (string.Equals(fileName, "diskpart.exe", StringComparison.OrdinalIgnoreCase)) { - return new ProcessExecutionResult { ExitCode = 0, StandardOutput = DiskPartOutput }; + return new ProcessExecutionResult { ExitCode = 0, StandardOutput = CreateDiskPartOutput() }; } return new ProcessExecutionResult { ExitCode = 0 }; } + + private string CreateDiskPartOutput() + { + string lastCall = Calls[^1]; + string scriptPath = lastCall.Replace("diskpart.exe /s ", string.Empty, StringComparison.Ordinal).Trim('"'); + string script = File.Exists(scriptPath) ? File.ReadAllText(scriptPath) : string.Empty; + ScriptContents.Add(script); + + if (script.Contains("detail partition", StringComparison.OrdinalIgnoreCase)) + { + if (script.Contains("select partition 1", StringComparison.OrdinalIgnoreCase)) + { + return "Type : c12a7328-f81f-11d2-ba4b-00a0c93ec93b"; + } + + if (script.Contains("select partition 3", StringComparison.OrdinalIgnoreCase)) + { + return "Type : de94bba4-06d1-4d40-a16a-bfd50179d6ac"; + } + + if (script.Contains("select partition 4", StringComparison.OrdinalIgnoreCase)) + { + return "Type : ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"; + } + } + + return DiskPartOutput; + } } } diff --git a/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs b/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs index 1987ece1..564098c4 100644 --- a/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs +++ b/src/Foundry.Deploy/Services/Deployment/WindowsDeploymentService.cs @@ -18,7 +18,9 @@ public sealed class WindowsDeploymentService : IWindowsDeploymentService private const int MsrPartitionSizeMb = 16; private const int RecoveryPartitionSizeMb = 5120; private const string RecoveryPartitionLabel = "Recovery"; + private const string EfiPartitionGuid = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"; private const string RecoveryPartitionGuid = "de94bba4-06d1-4d40-a16a-bfd50179d6ac"; + private const string BasicDataPartitionGuid = "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"; private const string RecoveryPartitionAttributes = "0x8000000000000001"; private const string WinReImageFileName = "winre.wim"; private readonly IProcessRunner _processRunner; @@ -201,13 +203,19 @@ await File.WriteAllLinesAsync( } IReadOnlyList partitions = DiskPartOutputParser.ParseListPartition(execution.StandardOutput); - DiskPartPartition? system = partitions.FirstOrDefault(partition => IsPartitionType(partition, "System")); - DiskPartPartition? recovery = partitions.FirstOrDefault(partition => IsPartitionType(partition, "Recovery")); + Dictionary partitionTypeGuids = await ReadPartitionTypeGuidsAsync( + diskNumber, + partitions, + workingDirectory, + cancellationToken).ConfigureAwait(false); + + DiskPartPartition? system = partitions.FirstOrDefault(partition => IsPartitionType(partition, partitionTypeGuids, EfiPartitionGuid)); + DiskPartPartition? recovery = partitions.FirstOrDefault(partition => IsPartitionType(partition, partitionTypeGuids, RecoveryPartitionGuid)); DiskPartPartition? windows = recovery is null ? null : partitions.FirstOrDefault(partition => partition.Number > recovery.Number && - IsPartitionType(partition, "Primary")); + IsPartitionType(partition, partitionTypeGuids, BasicDataPartitionGuid)); if (system is null || recovery is null || windows is null || recovery.Number >= windows.Number) { @@ -223,6 +231,44 @@ await File.WriteAllLinesAsync( }; } + private async Task> ReadPartitionTypeGuidsAsync( + int diskNumber, + IReadOnlyList partitions, + string workingDirectory, + CancellationToken cancellationToken) + { + var result = new Dictionary(); + foreach (DiskPartPartition partition in partitions) + { + string scriptPath = Path.Combine(workingDirectory, $"diskpart-partition-{partition.Number}-detail.txt"); + await File.WriteAllLinesAsync( + scriptPath, + [ + $"select disk {diskNumber}", + $"select partition {partition.Number}", + "detail partition" + ], + cancellationToken).ConfigureAwait(false); + + ProcessExecutionResult execution = await _processRunner + .RunAsync("diskpart.exe", $"/s \"{scriptPath}\"", workingDirectory, cancellationToken) + .ConfigureAwait(false); + + if (!execution.IsSuccess || string.IsNullOrWhiteSpace(execution.StandardOutput)) + { + continue; + } + + string typeGuid = DiskPartOutputParser.ParseDetailPartitionTypeGuid(execution.StandardOutput); + if (typeGuid.Length > 0) + { + result[partition.Number] = typeGuid; + } + } + + return result; + } + /// public async Task ResolveImageIndexAsync( string imagePath, @@ -1042,8 +1088,12 @@ private static char GetAvailableLetter(HashSet usedLetters, IReadOnlyList< throw new InvalidOperationException("No drive letter is available for deployment partitions."); } - private static bool IsPartitionType(DiskPartPartition partition, string expectedType) - => partition.Type.Equals(expectedType, StringComparison.OrdinalIgnoreCase); + private static bool IsPartitionType( + DiskPartPartition partition, + IReadOnlyDictionary partitionTypeGuids, + string expectedTypeGuid) + => partitionTypeGuids.TryGetValue(partition.Number, out string? typeGuid) && + typeGuid.Equals(expectedTypeGuid, StringComparison.OrdinalIgnoreCase); private static void ResetWorkingDirectory(string path) { diff --git a/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs b/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs index 4acc75c0..8731aabd 100644 --- a/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs +++ b/src/Foundry.Deploy/Services/Runtime/RecoveryTargetDiskResolver.cs @@ -6,6 +6,7 @@ namespace Foundry.Deploy.Services.Runtime; public sealed class RecoveryTargetDiskResolver : IRecoveryTargetDiskResolver { + private const string RecoveryPartitionGuid = "de94bba4-06d1-4d40-a16a-bfd50179d6ac"; private const string RecoveryMarkerRelativePath = @"Recovery\WindowsRE\FoundryOsRecovery.json"; private readonly IProcessRunner _processRunner; private readonly ILogger _logger; @@ -57,9 +58,36 @@ internal RecoveryTargetDiskResolver( continue; } - foreach (DiskPartPartition partition in DiskPartOutputParser.ParseListPartition(partitionExecution.StandardOutput) - .Where(partition => partition.Type.Equals("Recovery", StringComparison.OrdinalIgnoreCase))) + foreach (DiskPartPartition partition in DiskPartOutputParser.ParseListPartition(partitionExecution.StandardOutput)) { + ProcessExecutionResult detailExecution = await RunDiskPartScriptAsync( + [ + $"select disk {disk.Number}", + $"select partition {partition.Number}", + "detail partition" + ], + cancellationToken).ConfigureAwait(false); + + if (!detailExecution.IsSuccess || string.IsNullOrWhiteSpace(detailExecution.StandardOutput)) + { + continue; + } + + if (!DiskPartOutputParser + .ParseDetailPartitionTypeGuid(detailExecution.StandardOutput) + .Equals(RecoveryPartitionGuid, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + char? existingLetter = DiskPartOutputParser.ParseDetailPartitionDriveLetter(detailExecution.StandardOutput); + if (existingLetter.HasValue && + _fileExists($@"{existingLetter.Value}:\{RecoveryMarkerRelativePath}")) + { + candidateDiskNumbers.Add(disk.Number); + continue; + } + char? letter = GetAvailableTemporaryLetter(usedLetters); if (letter is null) { diff --git a/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs b/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs index ede6de98..a7672dee 100644 --- a/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs +++ b/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs @@ -14,7 +14,7 @@ public static IReadOnlyList ParseListDisk(string output) var disks = new List(); foreach (string line in SplitLines(output)) { - Match match = Regex.Match(line, @"^\s*\*?\s*(?:Disk|Disque)\s+(?\d+)\s+(?\S+)", RegexOptions.IgnoreCase); + Match match = Regex.Match(line, @"^\s*\*?\s*\D+?(?\d+)\s+(?[^\s-]+)", RegexOptions.IgnoreCase); if (!match.Success) { continue; @@ -24,7 +24,7 @@ public static IReadOnlyList ParseListDisk(string output) int.Parse(match.Groups["number"].Value), ParseFirstSizeBytes(line), line.TrimEnd().EndsWith('*'), - !match.Groups["status"].Value.Equals("Online", StringComparison.OrdinalIgnoreCase))); + IsOfflineStatus(match.Groups["status"].Value))); } return disks; @@ -40,10 +40,7 @@ public static IReadOnlyList ParseListPartition(string output) var partitions = new List(); foreach (string line in SplitLines(output)) { - Match match = Regex.Match( - line, - @"^\s*(?:Partition)\s+(?\d+)\s+(?\S+)", - RegexOptions.IgnoreCase); + Match match = Regex.Match(line, @"^\s*\D+?(?\d+)\s+(?[^\s-]+)", RegexOptions.IgnoreCase); if (!match.Success) { @@ -82,38 +79,37 @@ public static DiskPartDetailDisk ParseDetailDisk(int diskNumber, string output, continue; } - if (TryReadKeyValue(line, "Serial Number", out string serial)) + if (TryReadKeyValue(line, @".*serial.*|.*s.rie.*", out string serial)) { serialNumber = serial; continue; } - if (TryReadKeyValue(line, "Type", out string type)) + if (TryReadKeyValue(line, @"type", out string type)) { busType = type; continue; } - if (TryReadKeyValue(line, "Status", out string status)) + if (TryReadKeyValue(line, @"status|statut", out string status)) { - isOffline = !status.Equals("Online", StringComparison.OrdinalIgnoreCase); + isOffline = IsOfflineStatus(status); continue; } - if (TryReadKeyValue(line, "Current Read-only State", out string currentReadOnly) || - TryReadKeyValue(line, "Read-only", out currentReadOnly)) + if (TryReadKeyValue(line, @".*read-only.*|.*lecture\s+seule.*", out string currentReadOnly)) { isReadOnly = IsYes(currentReadOnly); continue; } - if (TryReadKeyValue(line, "Boot Disk", out string bootDisk)) + if (TryReadKeyValue(line, @".*boot\s+disk.*|.*d.marrage.*", out string bootDisk)) { isBoot = IsYes(bootDisk); continue; } - if (TryReadKeyValue(line, "System Disk", out string systemDisk)) + if (TryReadKeyValue(line, @".*system\s+disk.*|.*syst.me.*", out string systemDisk)) { isSystem = IsYes(systemDisk); } @@ -146,12 +142,41 @@ public static DiskPartDetailDisk ParseDetailDisk(int diskNumber, string output, return null; } + public static string ParseDetailPartitionTypeGuid(string output) + { + Match match = Regex.Match( + output, + @"(?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})", + RegexOptions.IgnoreCase); + + return match.Success + ? match.Groups["guid"].Value.ToLowerInvariant() + : string.Empty; + } + + public static char? ParseDetailPartitionDriveLetter(string output) + { + foreach (string line in SplitLines(output)) + { + Match match = Regex.Match(line, @"^\s*(?:Volume)\s+\d+\s+(?[A-Z])\b", RegexOptions.IgnoreCase); + if (match.Success) + { + return char.ToUpperInvariant(match.Groups["letter"].Value[0]); + } + } + + return null; + } + private static IReadOnlyList SplitLines(string output) => output.Split(["\r\n", "\n"], StringSplitOptions.None); private static ulong ParseFirstSizeBytes(string line) { - Match match = Regex.Match(line, @"(?\d+)\s*(?B|KB|MB|GB|TB)\b", RegexOptions.IgnoreCase); + Match match = Regex.Match( + line, + @"(?\d+)\s*(?KB|MB|GB|TB|K|M|G|T|B|octets)(?:\s*octets)?", + RegexOptions.IgnoreCase); if (!match.Success) { return 0; @@ -160,19 +185,19 @@ private static ulong ParseFirstSizeBytes(string line) ulong value = ulong.Parse(match.Groups["value"].Value); return match.Groups["unit"].Value.ToUpperInvariant() switch { - "B" => value, - "KB" => value * 1024UL, - "MB" => value * 1024UL * 1024UL, - "GB" => value * 1024UL * 1024UL * 1024UL, - "TB" => value * 1024UL * 1024UL * 1024UL * 1024UL, + "B" or "OCTETS" => value, + "K" or "KB" => value * 1024UL, + "M" or "MB" => value * 1024UL * 1024UL, + "G" or "GB" => value * 1024UL * 1024UL * 1024UL, + "T" or "TB" => value * 1024UL * 1024UL * 1024UL * 1024UL, _ => 0 }; } - private static bool TryReadKeyValue(string line, string key, out string value) + private static bool TryReadKeyValue(string line, string keyPattern, out string value) { value = string.Empty; - Match match = Regex.Match(line, $"^{Regex.Escape(key)}\\s*:\\s*(?.*)$", RegexOptions.IgnoreCase); + Match match = Regex.Match(line, $"^(?:{keyPattern})\\s*:\\s*(?.*)$", RegexOptions.IgnoreCase); if (!match.Success) { return false; @@ -185,6 +210,10 @@ private static bool TryReadKeyValue(string line, string key, out string value) private static bool IsYes(string value) => value.Equals("Yes", StringComparison.OrdinalIgnoreCase) || value.Equals("Oui", StringComparison.OrdinalIgnoreCase); + + private static bool IsOfflineStatus(string value) + => value.Contains("offline", StringComparison.OrdinalIgnoreCase) || + value.Contains("hors", StringComparison.OrdinalIgnoreCase); } internal sealed record DiskPartDisk(int Number, ulong SizeBytes, bool IsGpt, bool IsOffline); From 32712fede2c94c5b6f4c2234ec03b4915a6080d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Mon, 15 Jun 2026 22:47:37 +0200 Subject: [PATCH 07/11] fix(deploy): remove powershell hardware detection --- .../HardwareProfileServiceTests.cs | 112 +++++--- .../IAutopilotHardwareHashCaptureService.cs | 2 +- .../Deployment/DeploymentRuntimeState.cs | 4 +- .../Hardware/HardwareProfileService.cs | 253 +++++------------- .../Hardware/HardwareProfileSnapshot.cs | 11 + .../Hardware/IHardwareProfileSource.cs | 6 + .../Hardware/WindowsHardwareProfileSource.cs | 211 +++++++++++++++ 7 files changed, 374 insertions(+), 225 deletions(-) create mode 100644 src/Foundry.Deploy/Services/Hardware/HardwareProfileSnapshot.cs create mode 100644 src/Foundry.Deploy/Services/Hardware/IHardwareProfileSource.cs create mode 100644 src/Foundry.Deploy/Services/Hardware/WindowsHardwareProfileSource.cs diff --git a/src/Foundry.Deploy.Tests/HardwareProfileServiceTests.cs b/src/Foundry.Deploy.Tests/HardwareProfileServiceTests.cs index 12f32df3..0b20bd0c 100644 --- a/src/Foundry.Deploy.Tests/HardwareProfileServiceTests.cs +++ b/src/Foundry.Deploy.Tests/HardwareProfileServiceTests.cs @@ -1,6 +1,5 @@ using Foundry.Deploy.Models; using Foundry.Deploy.Services.Hardware; -using Foundry.Deploy.Services.System; using Microsoft.Extensions.Logging.Abstractions; namespace Foundry.Deploy.Tests; @@ -8,52 +7,99 @@ namespace Foundry.Deploy.Tests; public sealed class HardwareProfileServiceTests { [Fact] - public async Task GetCurrentAsync_WhenPowerShellIsUnavailable_ReturnsFallbackProfileWithoutStartingPowerShell() + public async Task GetCurrentAsync_WhenNativeSourceReturnsHardwareData_MapsProfile() { - var processRunner = new RecordingProcessRunner(); + var source = new FakeHardwareProfileSource + { + Snapshot = new HardwareProfileSnapshot( + Manufacturer: " Dell Inc. ", + Model: " Latitude 7450 ", + Product: " 1.0 ", + SerialNumber: " ABC123 ", + IsOnBattery: true, + Devices: + [ + new PnpDeviceInfo + { + Name = "System Firmware", + DeviceId = @"UEFI\RES_{9f41a8c2-5f6c-4f1d-9b8c-7a6e5d4c3b2a}", + ClassGuid = "{f2e7dd72-6468-4e36-b6f1-6488f42c1b52}", + HardwareIds = [@"UEFI\RES_{9f41a8c2-5f6c-4f1d-9b8c-7a6e5d4c3b2a}"] + }, + new PnpDeviceInfo + { + Name = "Trusted Platform Module 2.0", + DeviceId = @"ACPI\MSFT0101\1", + PnpClass = "SecurityDevices", + HardwareIds = [@"ACPI\MSFT0101"] + } + ]) + }; var service = new HardwareProfileService( - processRunner, + source, NullLogger.Instance, - _ => false); + () => "AMD64"); + + HardwareProfile profile = await service.GetCurrentAsync(TestContext.Current.CancellationToken); + + Assert.Equal("Dell", profile.Manufacturer); + Assert.Equal("Latitude 7450", profile.Model); + Assert.Equal("1.0", profile.Product); + Assert.Equal("ABC123", profile.SerialNumber); + Assert.Equal("x64", profile.Architecture); + Assert.True(profile.IsOnBattery); + Assert.True(profile.IsTpmPresent); + Assert.Equal("9f41a8c2-5f6c-4f1d-9b8c-7a6e5d4c3b2a", profile.SystemFirmwareHardwareId); + Assert.Equal(2, profile.PnpDevices.Count); + Assert.Equal(1, source.Calls); + } + + [Fact] + public async Task GetCurrentAsync_WhenNativeSourceFails_ReturnsFallbackProfile() + { + var source = new FakeHardwareProfileSource + { + Exception = new InvalidOperationException("SetupAPI unavailable") + }; + var service = new HardwareProfileService( + source, + NullLogger.Instance, + () => string.Empty); HardwareProfile profile = await service.GetCurrentAsync(TestContext.Current.CancellationToken); Assert.Equal("Unknown", profile.Manufacturer); - Assert.Empty(processRunner.Calls); + Assert.Equal("Unknown", profile.Model); + Assert.Equal("Unknown", profile.Product); + Assert.Equal("Unknown", profile.SerialNumber); + Assert.False(profile.IsOnBattery); + Assert.False(profile.IsTpmPresent); + Assert.Empty(profile.SystemFirmwareHardwareId); + Assert.Empty(profile.PnpDevices); } - private sealed class RecordingProcessRunner : IProcessRunner + private sealed class FakeHardwareProfileSource : IHardwareProfileSource { - public List Calls { get; } = []; + public HardwareProfileSnapshot Snapshot { get; init; } = new( + Manufacturer: string.Empty, + Model: string.Empty, + Product: string.Empty, + SerialNumber: string.Empty, + IsOnBattery: false, + Devices: []); - public Task RunAsync( - string fileName, - string arguments, - string workingDirectory, - CancellationToken cancellationToken = default) - { - Calls.Add(fileName); - return Task.FromResult(new ProcessExecutionResult { ExitCode = 1 }); - } + public Exception? Exception { get; init; } + public int Calls { get; private set; } - public Task RunAsync( - string fileName, - IEnumerable arguments, - string workingDirectory, - CancellationToken cancellationToken = default) + public HardwareProfileSnapshot Capture() { - return RunAsync(fileName, string.Join(' ', arguments), workingDirectory, cancellationToken); - } + Calls++; + if (Exception is not null) + { + throw Exception; + } - public Task RunAsync( - string fileName, - IEnumerable arguments, - string workingDirectory, - Action? onOutputData, - Action? onErrorData, - CancellationToken cancellationToken = default) - { - return RunAsync(fileName, arguments, workingDirectory, cancellationToken); + return Snapshot; } } } diff --git a/src/Foundry.Deploy/Services/Autopilot/IAutopilotHardwareHashCaptureService.cs b/src/Foundry.Deploy/Services/Autopilot/IAutopilotHardwareHashCaptureService.cs index b1016519..98e0673a 100644 --- a/src/Foundry.Deploy/Services/Autopilot/IAutopilotHardwareHashCaptureService.cs +++ b/src/Foundry.Deploy/Services/Autopilot/IAutopilotHardwareHashCaptureService.cs @@ -1,7 +1,7 @@ namespace Foundry.Deploy.Services.Autopilot; /// -/// Captures an Autopilot hardware hash from WinPE without using PowerShell implementation logic. +/// Captures an Autopilot hardware hash from WinPE using OA3Tool. /// public interface IAutopilotHardwareHashCaptureService { diff --git a/src/Foundry.Deploy/Services/Deployment/DeploymentRuntimeState.cs b/src/Foundry.Deploy/Services/Deployment/DeploymentRuntimeState.cs index 1d625bda..3be26fa9 100644 --- a/src/Foundry.Deploy/Services/Deployment/DeploymentRuntimeState.cs +++ b/src/Foundry.Deploy/Services/Deployment/DeploymentRuntimeState.cs @@ -157,7 +157,7 @@ public sealed record DeploymentRuntimeState public string? PreOobeSetupCompletePath { get; set; } /// - /// Gets or sets the offline path to the generated pre-OOBE PowerShell runner. + /// Gets or sets the offline path to the generated pre-OOBE runner. /// public string? PreOobeRunnerPath { get; set; } @@ -167,7 +167,7 @@ public sealed record DeploymentRuntimeState public string? PreOobeManifestPath { get; set; } /// - /// Gets or sets the offline paths to staged pre-OOBE PowerShell scripts. + /// Gets or sets the offline paths to staged pre-OOBE scripts. /// public IReadOnlyList PreOobeScriptPaths { get; set; } = []; diff --git a/src/Foundry.Deploy/Services/Hardware/HardwareProfileService.cs b/src/Foundry.Deploy/Services/Hardware/HardwareProfileService.cs index 5b3811c5..20a9b132 100644 --- a/src/Foundry.Deploy/Services/Hardware/HardwareProfileService.cs +++ b/src/Foundry.Deploy/Services/Hardware/HardwareProfileService.cs @@ -1,136 +1,41 @@ -using System.Text.Json; -using System.Text; -using System.IO; +using System.Text.RegularExpressions; using Foundry.Deploy.Models; -using Foundry.Deploy.Services.System; using Microsoft.Extensions.Logging; namespace Foundry.Deploy.Services.Hardware; -public sealed class HardwareProfileService : IHardwareProfileService +public sealed partial class HardwareProfileService : IHardwareProfileService { - private readonly IProcessRunner _processRunner; + private const string SystemFirmwareClassGuid = "{f2e7dd72-6468-4e36-b6f1-6488f42c1b52}"; + + private readonly IHardwareProfileSource _source; private readonly ILogger _logger; - private readonly Func _fileExists; + private readonly Func _architectureProvider; - public HardwareProfileService(IProcessRunner processRunner, ILogger logger) - : this(processRunner, logger, File.Exists) + public HardwareProfileService(ILogger logger) + : this(new WindowsHardwareProfileSource(), logger, () => Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") ?? string.Empty) { } internal HardwareProfileService( - IProcessRunner processRunner, + IHardwareProfileSource source, ILogger logger, - Func fileExists) + Func architectureProvider) { - _processRunner = processRunner; + _source = source; _logger = logger; - _fileExists = fileExists; + _architectureProvider = architectureProvider; } - public async Task GetCurrentAsync(CancellationToken cancellationToken = default) + public Task GetCurrentAsync(CancellationToken cancellationToken = default) { + cancellationToken.ThrowIfCancellationRequested(); _logger.LogInformation("Detecting current hardware profile."); - string powershellPath = ResolvePowerShellPath(); - if (!_fileExists(powershellPath)) - { - _logger.LogWarning("PowerShell was not found. Using fallback hardware profile. Path={PowerShellPath}", powershellPath); - return BuildFallbackProfile(); - } - - string script = @" -function ConvertTo-TrimmedString { - param ( - [Parameter(ValueFromPipeline = $true)] - $Value - ) - - process { - if ($null -eq $Value) { - return '' - } - - return $Value.ToString().Trim() - } -} - -$computer = Get-CimInstance -ClassName Win32_ComputerSystem -$product = Get-CimInstance -ClassName Win32_ComputerSystemProduct -$bios = Get-CimInstance -ClassName Win32_BIOS -$tpm = Get-CimInstance -Namespace 'ROOT\cimv2\Security\MicrosoftTpm' -ClassName Win32_Tpm -ErrorAction SilentlyContinue -$battery = Get-CimInstance -ClassName Win32_Battery -ErrorAction SilentlyContinue -$pnpDevices = @(Get-CimInstance -ClassName Win32_PnpEntity -Property Name,DeviceID,HardwareID,ClassGuid,Manufacturer,PNPClass -ErrorAction SilentlyContinue | ForEach-Object { - $hardwareIds = @($_.HardwareID | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { $_.ToString().Trim() }) - [pscustomobject]@{ - Name = [string]($_.Name | ConvertTo-TrimmedString) - DeviceId = [string]($_.DeviceID | ConvertTo-TrimmedString) - HardwareIds = $hardwareIds - ClassGuid = [string]($_.ClassGuid | ConvertTo-TrimmedString) - Manufacturer = [string]($_.Manufacturer | ConvertTo-TrimmedString) - PnpClass = [string]($_.PNPClass | ConvertTo-TrimmedString) - } -}) -$firmwareDevice = $pnpDevices | Where-Object { $_.ClassGuid -eq '{f2e7dd72-6468-4e36-b6f1-6488f42c1b52}' } | Select-Object -First 1 -$systemFirmwareHardwareId = '' -if ($firmwareDevice -and $firmwareDevice.DeviceId -match '\{?(([0-9a-f]){8}-([0-9a-f]){4}-([0-9a-f]){4}-([0-9a-f]){4}-([0-9a-f]){12})\}?') { - $systemFirmwareHardwareId = $Matches[1] -} -$isOnBattery = @($battery | Where-Object { $_.BatteryStatus -eq 1 }).Count -gt 0 - -[pscustomobject]@{ - Manufacturer = [string]$computer.Manufacturer - Model = [string]$computer.Model - Product = [string]$product.Version - SerialNumber = [string]$bios.SerialNumber - Architecture = [string]$env:PROCESSOR_ARCHITECTURE - IsOnBattery = [bool]$isOnBattery - IsTpmPresent = [bool]($null -ne $tpm) - SystemFirmwareHardwareId = [string]$systemFirmwareHardwareId - PnpDevices = $pnpDevices -} | ConvertTo-Json -Compress -Depth 8 -"; - - string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(script)); - string args = $"-NoProfile -ExecutionPolicy Bypass -EncodedCommand {encoded}"; - ProcessExecutionResult execution = await _processRunner - .RunAsync(powershellPath, args, Path.GetTempPath(), cancellationToken) - .ConfigureAwait(false); - - if (!execution.IsSuccess || string.IsNullOrWhiteSpace(execution.StandardOutput)) - { - _logger.LogWarning("Hardware profile detection returned no data. Using fallback profile. ExitCode={ExitCode}", execution.ExitCode); - return BuildFallbackProfile(); - } try { - using JsonDocument document = JsonDocument.Parse(execution.StandardOutput); - JsonElement root = document.RootElement; - - string manufacturer = ReadProperty(root, "Manufacturer"); - string model = ReadProperty(root, "Model"); - string product = ReadProperty(root, "Product"); - string serial = ReadProperty(root, "SerialNumber"); - string architecture = NormalizeArchitecture(ReadProperty(root, "Architecture")); - bool isVirtualMachine = IsVirtualMachine(manufacturer, model, product); - bool isOnBattery = ReadBoolProperty(root, "IsOnBattery"); - bool isTpmPresent = ReadBoolProperty(root, "IsTpmPresent"); - string systemFirmwareHardwareId = ReadProperty(root, "SystemFirmwareHardwareId"); - IReadOnlyList pnpDevices = ReadPnpDevices(root); - - HardwareProfile profile = new() - { - Manufacturer = NormalizeManufacturer(manufacturer), - Model = NormalizeValue(model), - Product = NormalizeValue(product), - SerialNumber = NormalizeValue(serial), - Architecture = architecture, - IsVirtualMachine = isVirtualMachine, - IsOnBattery = isOnBattery, - IsTpmPresent = isTpmPresent, - SystemFirmwareHardwareId = systemFirmwareHardwareId.Trim(), - PnpDevices = pnpDevices - }; + HardwareProfileSnapshot snapshot = _source.Capture(); + HardwareProfile profile = BuildProfile(snapshot); _logger.LogInformation("Hardware profile detected. Manufacturer={Manufacturer}, Model={Model}, Architecture={Architecture}, IsVirtualMachine={IsVirtualMachine}, IsOnBattery={IsOnBattery}, IsTpmPresent={IsTpmPresent}", profile.Manufacturer, @@ -139,25 +44,48 @@ function ConvertTo-TrimmedString { profile.IsVirtualMachine, profile.IsOnBattery, profile.IsTpmPresent); - return profile; + + return Task.FromResult(profile); } catch (Exception ex) { - _logger.LogError(ex, "Failed to parse hardware profile payload. Falling back to default profile."); - return BuildFallbackProfile(); + _logger.LogWarning(ex, "Native hardware profile detection failed. Using fallback hardware profile."); + return Task.FromResult(BuildFallbackProfile()); } } - private static HardwareProfile BuildFallbackProfile() + private HardwareProfile BuildProfile(HardwareProfileSnapshot snapshot) + { + string manufacturer = NormalizeManufacturer(snapshot.Manufacturer); + string model = NormalizeValue(snapshot.Model); + string product = NormalizeValue(snapshot.Product); + string serial = NormalizeValue(snapshot.SerialNumber); + IReadOnlyList devices = snapshot.Devices; + + return new HardwareProfile + { + Manufacturer = manufacturer, + Model = model, + Product = product, + SerialNumber = serial, + Architecture = NormalizeArchitecture(_architectureProvider()), + IsVirtualMachine = IsVirtualMachine(manufacturer, model, product), + IsOnBattery = snapshot.IsOnBattery, + IsTpmPresent = HasTpmDevice(devices), + SystemFirmwareHardwareId = ResolveSystemFirmwareHardwareId(devices), + PnpDevices = devices + }; + } + + private HardwareProfile BuildFallbackProfile() { - string architecture = NormalizeArchitecture(Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") ?? string.Empty); return new HardwareProfile { Manufacturer = "Unknown", Model = "Unknown", Product = "Unknown", SerialNumber = "Unknown", - Architecture = architecture, + Architecture = NormalizeArchitecture(_architectureProvider()), IsVirtualMachine = false, IsOnBattery = false, IsTpmPresent = false, @@ -166,89 +94,33 @@ private static HardwareProfile BuildFallbackProfile() }; } - private static string ResolvePowerShellPath() - { - return Path.Combine( - Environment.SystemDirectory, - "WindowsPowerShell", - "v1.0", - "powershell.exe"); - } - - private static string ReadProperty(JsonElement root, string propertyName) - { - return root.TryGetProperty(propertyName, out JsonElement value) - ? value.GetString() ?? string.Empty - : string.Empty; - } - - private static bool ReadBoolProperty(JsonElement root, string propertyName) + private static string ResolveSystemFirmwareHardwareId(IReadOnlyList devices) { - if (!root.TryGetProperty(propertyName, out JsonElement value)) - { - return false; - } - - return value.ValueKind == JsonValueKind.True || - (value.ValueKind == JsonValueKind.String && bool.TryParse(value.GetString(), out bool parsed) && parsed); - } + PnpDeviceInfo? firmwareDevice = devices.FirstOrDefault(device => + string.Equals(device.ClassGuid, SystemFirmwareClassGuid, StringComparison.OrdinalIgnoreCase)); - private static IReadOnlyList ReadPnpDevices(JsonElement root) - { - if (!root.TryGetProperty("PnpDevices", out JsonElement devicesElement) || - devicesElement.ValueKind != JsonValueKind.Array) + if (firmwareDevice is null) { - return Array.Empty(); + return string.Empty; } - var devices = new List(); - foreach (JsonElement deviceElement in devicesElement.EnumerateArray()) + Match match = FirmwareHardwareIdRegex().Match(firmwareDevice.DeviceId); + if (!match.Success) { - if (deviceElement.ValueKind != JsonValueKind.Object) - { - continue; - } - - devices.Add(new PnpDeviceInfo - { - Name = ReadProperty(deviceElement, "Name"), - DeviceId = ReadProperty(deviceElement, "DeviceId"), - HardwareIds = ReadStringArrayProperty(deviceElement, "HardwareIds"), - ClassGuid = ReadProperty(deviceElement, "ClassGuid"), - Manufacturer = ReadProperty(deviceElement, "Manufacturer"), - PnpClass = ReadProperty(deviceElement, "PnpClass") - }); + match = firmwareDevice.HardwareIds + .Select(id => FirmwareHardwareIdRegex().Match(id)) + .FirstOrDefault(candidate => candidate.Success) ?? Match.Empty; } - return devices; + return match.Success ? match.Groups[1].Value : string.Empty; } - private static IReadOnlyList ReadStringArrayProperty(JsonElement root, string propertyName) + private static bool HasTpmDevice(IReadOnlyList devices) { - if (!root.TryGetProperty(propertyName, out JsonElement value)) - { - return Array.Empty(); - } - - if (value.ValueKind == JsonValueKind.Array) - { - return value - .EnumerateArray() - .Select(item => item.ValueKind == JsonValueKind.String ? item.GetString() ?? string.Empty : string.Empty) - .Where(item => !string.IsNullOrWhiteSpace(item)) - .Select(item => item.Trim()) - .ToArray(); - } - - if (value.ValueKind == JsonValueKind.String) - { - string? stringValue = value.GetString(); - return string.IsNullOrWhiteSpace(stringValue) - ? Array.Empty() - : [stringValue.Trim()]; - } - - return Array.Empty(); + return devices.Any(device => + device.HardwareIds.Any(id => id.Contains("MSFT0101", StringComparison.OrdinalIgnoreCase)) || + device.Name.Contains("Trusted Platform Module", StringComparison.OrdinalIgnoreCase) || + device.PnpClass.Contains("SecurityDevices", StringComparison.OrdinalIgnoreCase)); } private static bool IsVirtualMachine(string manufacturer, string model, string product) @@ -316,4 +188,7 @@ private static string NormalizeValue(string value) string normalized = value.Trim(); return string.IsNullOrWhiteSpace(normalized) ? "Unknown" : normalized; } + + [GeneratedRegex(@"\{?(([0-9a-f]){8}-([0-9a-f]){4}-([0-9a-f]){4}-([0-9a-f]){4}-([0-9a-f]){12})\}?", RegexOptions.IgnoreCase)] + private static partial Regex FirmwareHardwareIdRegex(); } diff --git a/src/Foundry.Deploy/Services/Hardware/HardwareProfileSnapshot.cs b/src/Foundry.Deploy/Services/Hardware/HardwareProfileSnapshot.cs new file mode 100644 index 00000000..0ba59ef8 --- /dev/null +++ b/src/Foundry.Deploy/Services/Hardware/HardwareProfileSnapshot.cs @@ -0,0 +1,11 @@ +using Foundry.Deploy.Models; + +namespace Foundry.Deploy.Services.Hardware; + +internal sealed record HardwareProfileSnapshot( + string Manufacturer, + string Model, + string Product, + string SerialNumber, + bool IsOnBattery, + IReadOnlyList Devices); diff --git a/src/Foundry.Deploy/Services/Hardware/IHardwareProfileSource.cs b/src/Foundry.Deploy/Services/Hardware/IHardwareProfileSource.cs new file mode 100644 index 00000000..289a8e35 --- /dev/null +++ b/src/Foundry.Deploy/Services/Hardware/IHardwareProfileSource.cs @@ -0,0 +1,6 @@ +namespace Foundry.Deploy.Services.Hardware; + +internal interface IHardwareProfileSource +{ + HardwareProfileSnapshot Capture(); +} diff --git a/src/Foundry.Deploy/Services/Hardware/WindowsHardwareProfileSource.cs b/src/Foundry.Deploy/Services/Hardware/WindowsHardwareProfileSource.cs new file mode 100644 index 00000000..daa49217 --- /dev/null +++ b/src/Foundry.Deploy/Services/Hardware/WindowsHardwareProfileSource.cs @@ -0,0 +1,211 @@ +using System.Runtime.InteropServices; +using System.Text; +using Foundry.Deploy.Models; +using Microsoft.Win32; + +namespace Foundry.Deploy.Services.Hardware; + +internal sealed class WindowsHardwareProfileSource : IHardwareProfileSource +{ + private const string BiosRegistryPath = @"HARDWARE\DESCRIPTION\System\BIOS"; + private const uint DigcfPresent = 0x00000002; + private const uint DigcfAllClasses = 0x00000004; + private const uint SpdrpDevicedesc = 0x00000000; + private const uint SpdrpHardwareId = 0x00000001; + private const uint SpdrpMfg = 0x0000000B; + private const uint SpdrpClass = 0x00000007; + private const uint SpdrpClassGuid = 0x00000008; + private const uint SpdrpFriendlyName = 0x0000000C; + private static readonly IntPtr InvalidHandleValue = new(-1); + + public HardwareProfileSnapshot Capture() + { + IReadOnlyList devices = EnumeratePnpDevices(); + + return new HardwareProfileSnapshot( + Manufacturer: ReadBiosValue("SystemManufacturer"), + Model: ReadBiosValue("SystemProductName"), + Product: ReadBiosValue("SystemVersion"), + SerialNumber: ReadBiosValue("SystemSerialNumber"), + IsOnBattery: IsSystemOnBattery(), + Devices: devices); + } + + private static string ReadBiosValue(string name) + { + try + { + using RegistryKey? key = Registry.LocalMachine.OpenSubKey(BiosRegistryPath); + return key?.GetValue(name)?.ToString() ?? string.Empty; + } + catch + { + return string.Empty; + } + } + + private static bool IsSystemOnBattery() + { + return GetSystemPowerStatus(out SystemPowerStatus status) && status.ACLineStatus == 0; + } + + private static IReadOnlyList EnumeratePnpDevices() + { + IntPtr deviceInfoSet = SetupDiGetClassDevs(IntPtr.Zero, null, IntPtr.Zero, DigcfPresent | DigcfAllClasses); + if (deviceInfoSet == InvalidHandleValue) + { + return []; + } + + try + { + var devices = new List(); + for (uint index = 0; ; index++) + { + SpDevinfoData deviceInfoData = new() + { + CbSize = (uint)Marshal.SizeOf() + }; + + if (!SetupDiEnumDeviceInfo(deviceInfoSet, index, ref deviceInfoData)) + { + break; + } + + string friendlyName = ReadDeviceProperty(deviceInfoSet, ref deviceInfoData, SpdrpFriendlyName); + string description = ReadDeviceProperty(deviceInfoSet, ref deviceInfoData, SpdrpDevicedesc); + devices.Add(new PnpDeviceInfo + { + Name = FirstNonEmpty(friendlyName, description), + DeviceId = ReadDeviceInstanceId(deviceInfoSet, ref deviceInfoData), + HardwareIds = ReadDeviceMultiStringProperty(deviceInfoSet, ref deviceInfoData, SpdrpHardwareId), + ClassGuid = ReadDeviceProperty(deviceInfoSet, ref deviceInfoData, SpdrpClassGuid), + Manufacturer = ReadDeviceProperty(deviceInfoSet, ref deviceInfoData, SpdrpMfg), + PnpClass = ReadDeviceProperty(deviceInfoSet, ref deviceInfoData, SpdrpClass) + }); + } + + return devices; + } + finally + { + SetupDiDestroyDeviceInfoList(deviceInfoSet); + } + } + + private static string ReadDeviceProperty(IntPtr deviceInfoSet, ref SpDevinfoData deviceInfoData, uint property) + { + IReadOnlyList values = ReadDeviceMultiStringProperty(deviceInfoSet, ref deviceInfoData, property); + return values.Count > 0 ? values[0] : string.Empty; + } + + private static IReadOnlyList ReadDeviceMultiStringProperty(IntPtr deviceInfoSet, ref SpDevinfoData deviceInfoData, uint property) + { + byte[] buffer = new byte[8192]; + return SetupDiGetDeviceRegistryProperty( + deviceInfoSet, + ref deviceInfoData, + property, + out _, + buffer, + (uint)buffer.Length, + out uint requiredSize) + ? DecodeRegistryString(buffer, requiredSize) + : []; + } + + private static string ReadDeviceInstanceId(IntPtr deviceInfoSet, ref SpDevinfoData deviceInfoData) + { + var buffer = new char[1024]; + return SetupDiGetDeviceInstanceId( + deviceInfoSet, + ref deviceInfoData, + buffer, + (uint)buffer.Length, + out _) + ? new string(buffer).TrimEnd('\0') + : string.Empty; + } + + private static IReadOnlyList DecodeRegistryString(byte[] buffer, uint requiredSize) + { + if (requiredSize == 0) + { + return []; + } + + int byteCount = Math.Min((int)requiredSize, buffer.Length); + string raw = Encoding.Unicode.GetString(buffer, 0, byteCount).TrimEnd('\0'); + if (string.IsNullOrWhiteSpace(raw)) + { + return []; + } + + return raw + .Split('\0', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .ToArray(); + } + + private static string FirstNonEmpty(params string[] values) + { + return values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)) ?? string.Empty; + } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool GetSystemPowerStatus(out SystemPowerStatus systemPowerStatus); + + [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern IntPtr SetupDiGetClassDevs( + IntPtr classGuid, + string? enumerator, + IntPtr hwndParent, + uint flags); + + [DllImport("setupapi.dll", SetLastError = true)] + private static extern bool SetupDiEnumDeviceInfo( + IntPtr deviceInfoSet, + uint memberIndex, + ref SpDevinfoData deviceInfoData); + + [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool SetupDiGetDeviceRegistryProperty( + IntPtr deviceInfoSet, + ref SpDevinfoData deviceInfoData, + uint property, + out uint propertyRegDataType, + byte[] propertyBuffer, + uint propertyBufferSize, + out uint requiredSize); + + [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool SetupDiGetDeviceInstanceId( + IntPtr deviceInfoSet, + ref SpDevinfoData deviceInfoData, + char[] deviceInstanceId, + uint deviceInstanceIdSize, + out uint requiredSize); + + [DllImport("setupapi.dll", SetLastError = true)] + private static extern bool SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet); + + [StructLayout(LayoutKind.Sequential)] + private struct SystemPowerStatus + { + public byte ACLineStatus; + public byte BatteryFlag; + public byte BatteryLifePercent; + public byte SystemStatusFlag; + public int BatteryLifeTime; + public int BatteryFullLifeTime; + } + + [StructLayout(LayoutKind.Sequential)] + private struct SpDevinfoData + { + public uint CbSize; + public Guid ClassGuid; + public uint DevInst; + public IntPtr Reserved; + } +} From f727a08ce07ea23ee52e31da5b3c0d202e2b0c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Mon, 15 Jun 2026 23:47:40 +0200 Subject: [PATCH 08/11] fix(deploy): parse diskpart detail output reliably --- .../TargetDiskServiceTests.cs | 104 +++++++++++++++++- .../Services/System/DiskPartOutputParser.cs | 89 ++++++++++++++- 2 files changed, 186 insertions(+), 7 deletions(-) diff --git a/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs b/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs index b3efef8e..3e9a3610 100644 --- a/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs +++ b/src/Foundry.Deploy.Tests/TargetDiskServiceTests.cs @@ -56,9 +56,52 @@ public async Task GetDisksAsync_WhenDiskPartOutputIsLocalized_ParsesDiskInventor Assert.True(disk.IsSelectable); } - private sealed class DiskPartProcessRunner(bool localizedOutput) : IProcessRunner + [Fact] + public async Task GetDisksAsync_WhenDiskPartDetailIncludesBanner_UsesDiskModelAsFriendlyName() + { + var processRunner = new DiskPartProcessRunner(localizedOutput: false, includeBanner: true); + var service = new TargetDiskService(processRunner, NullLogger.Instance); + + IReadOnlyList disks = await service.GetDisksAsync(TestContext.Current.CancellationToken); + + TargetDiskInfo disk = Assert.Single(disks); + Assert.Equal("NVMe Foundry Disk", disk.FriendlyName); + } + + [Fact] + public async Task GetDisksAsync_WhenDiskPartSelectionTextIsUnknownLanguage_UsesDiskModelAsFriendlyName() + { + var processRunner = new DiskPartProcessRunner(localizedOutput: false, includeUnknownLanguageSelectionText: true); + var service = new TargetDiskService(processRunner, NullLogger.Instance); + + IReadOnlyList disks = await service.GetDisksAsync(TestContext.Current.CancellationToken); + + TargetDiskInfo disk = Assert.Single(disks); + Assert.Equal("NVMe Foundry Disk", disk.FriendlyName); + } + + [Fact] + public async Task GetDisksAsync_WhenDiskPartTypeKeyIsUnavailable_InfersBusTypeFromHardwareTokens() + { + var processRunner = new DiskPartProcessRunner(localizedOutput: false, omitTypeKey: true); + var service = new TargetDiskService(processRunner, NullLogger.Instance); + + IReadOnlyList disks = await service.GetDisksAsync(TestContext.Current.CancellationToken); + + TargetDiskInfo disk = Assert.Single(disks); + Assert.Equal("NVMe", disk.BusType); + } + + private sealed class DiskPartProcessRunner( + bool localizedOutput, + bool includeBanner = false, + bool includeUnknownLanguageSelectionText = false, + bool omitTypeKey = false) : IProcessRunner { private readonly bool _localizedOutput = localizedOutput; + private readonly bool _includeBanner = includeBanner; + private readonly bool _includeUnknownLanguageSelectionText = includeUnknownLanguageSelectionText; + private readonly bool _omitTypeKey = omitTypeKey; public List Calls { get; } = []; @@ -101,7 +144,7 @@ private ProcessExecutionResult CreateResult(string fileName, string arguments) string script = File.ReadAllText(arguments.Replace("/s ", string.Empty, StringComparison.Ordinal).Trim('"')); string output = script.Contains("detail disk", StringComparison.OrdinalIgnoreCase) - ? CreateDetailDiskOutput(script, _localizedOutput) + ? CreateDetailDiskOutput(script, _localizedOutput, _includeBanner, _includeUnknownLanguageSelectionText, _omitTypeKey) : script.Contains("detail volume", StringComparison.OrdinalIgnoreCase) ? """ Disk ### Status Size Free Dyn Gpt @@ -131,11 +174,16 @@ Disk 1 Online 32 GB 0 B }; } - private static string CreateDetailDiskOutput(string script, bool localizedOutput) + private static string CreateDetailDiskOutput( + string script, + bool localizedOutput, + bool includeBanner, + bool includeUnknownLanguageSelectionText, + bool omitTypeKey) { if (script.Contains("select disk 1", StringComparison.OrdinalIgnoreCase)) { - return localizedOutput + string usbOutput = localizedOutput ? """ Disque USB Foundry Type : USB @@ -154,9 +202,11 @@ USB Foundry Disk Boot Disk : No Serial Number : USB123 """; + + return WrapDetailOutput(usbOutput, includeBanner, includeUnknownLanguageSelectionText); } - return localizedOutput + string output = localizedOutput ? """ Disque Foundry NVMe Type : NVMe @@ -185,6 +235,50 @@ NVMe Foundry Disk Clustered Disk : No Serial Number : NVME123 """; + + if (omitTypeKey) + { + output = string.Join( + Environment.NewLine, + output + .Split(["\r\n", "\n"], StringSplitOptions.None) + .Where(line => !line.TrimStart().StartsWith("Type", StringComparison.OrdinalIgnoreCase))); + } + + return WrapDetailOutput(output, includeBanner, includeUnknownLanguageSelectionText); + } + + private static string WrapDetailOutput( + string detailOutput, + bool includeBanner, + bool includeUnknownLanguageSelectionText) + { + if (includeBanner) + { + return AddBanner(detailOutput, selectionText: "Disk 0 is now the selected disk."); + } + + return includeUnknownLanguageSelectionText + ? AddBanner(detailOutput, selectionText: "LOCALIZED_SELECTION_CONFIRMATION_WITHOUT_COLON") + : detailOutput; + } + + private static string AddBanner(string detailOutput, string selectionText) + { + return $""" + Microsoft DiskPart version 10.0.26100.1 + + Copyright (C) Microsoft Corporation. + On computer: MININT-FOUND + + DISKPART> select disk 0 + + {selectionText} + + DISKPART> detail disk + + {detailOutput} + """; } } } diff --git a/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs b/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs index a7672dee..d6c8fb48 100644 --- a/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs +++ b/src/Foundry.Deploy/Services/System/DiskPartOutputParser.cs @@ -60,6 +60,8 @@ public static DiskPartDetailDisk ParseDetailDisk(int diskNumber, string output, string friendlyName = string.Empty; string serialNumber = string.Empty; string busType = string.Empty; + string friendlyNameCandidate = string.Empty; + var busInferenceLines = new List(); bool isBoot = false; bool isSystem = false; bool isReadOnly = false; @@ -73,12 +75,22 @@ public static DiskPartDetailDisk ParseDetailDisk(int diskNumber, string output, continue; } - if (friendlyName.Length == 0 && !line.Contains(':', StringComparison.Ordinal)) + if (line.Contains(':', StringComparison.Ordinal) && friendlyName.Length == 0) { - friendlyName = line; + friendlyName = friendlyNameCandidate; + } + + if (!line.Contains(':', StringComparison.Ordinal) && !IsDiskPartBoilerplateLine(line)) + { + friendlyNameCandidate = line; continue; } + if (IsBusInferenceLine(line)) + { + busInferenceLines.Add(line); + } + if (TryReadKeyValue(line, @".*serial.*|.*s.rie.*", out string serial)) { serialNumber = serial; @@ -115,6 +127,11 @@ public static DiskPartDetailDisk ParseDetailDisk(int diskNumber, string output, } } + if (string.IsNullOrWhiteSpace(busType)) + { + busType = InferBusType(friendlyName, busInferenceLines); + } + return new DiskPartDetailDisk( diskNumber, friendlyName, @@ -207,6 +224,74 @@ private static bool TryReadKeyValue(string line, string keyPattern, out string v return true; } + private static bool IsDiskPartBoilerplateLine(string line) + { + return line.StartsWith("Microsoft DiskPart", StringComparison.OrdinalIgnoreCase) || + line.StartsWith("Copyright", StringComparison.OrdinalIgnoreCase) || + line.StartsWith("DISKPART>", StringComparison.OrdinalIgnoreCase); + } + + private static bool IsBusInferenceLine(string line) + { + if (!line.Contains(':', StringComparison.Ordinal)) + { + return false; + } + + return ResolveBusTypeFromText(line).Length > 0; + } + + private static string InferBusType(string friendlyName, IReadOnlyList inferenceLines) + { + return ResolveBusTypeFromText(string.Join('\n', inferenceLines.Prepend(friendlyName))); + } + + private static string ResolveBusTypeFromText(string value) + { + string combined = value.ToUpperInvariant(); + if (combined.Contains("NVME", StringComparison.Ordinal)) + { + return "NVMe"; + } + + if (combined.Contains("USB", StringComparison.Ordinal)) + { + return "USB"; + } + + if (combined.Contains("SATA", StringComparison.Ordinal)) + { + return "SATA"; + } + + if (combined.Contains("SCSI", StringComparison.Ordinal)) + { + return "SCSI"; + } + + if (combined.Contains("RAID", StringComparison.Ordinal)) + { + return "RAID"; + } + + if (combined.Contains("SAS", StringComparison.Ordinal)) + { + return "SAS"; + } + + if (combined.Contains("IDE", StringComparison.Ordinal)) + { + return "IDE"; + } + + if (combined.Contains("EMMC", StringComparison.Ordinal)) + { + return "eMMC"; + } + + return string.Empty; + } + private static bool IsYes(string value) => value.Equals("Yes", StringComparison.OrdinalIgnoreCase) || value.Equals("Oui", StringComparison.OrdinalIgnoreCase); From 78d461129290fcc8c201a139ff49e21c523d491f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Mon, 15 Jun 2026 23:47:46 +0200 Subject: [PATCH 09/11] fix(localization): update os recovery labels --- .../Strings/fr-CA/Resources.resx | 2 +- .../Strings/lv-LV/Resources.resx | 2 +- .../Strings/nl-NL/Resources.resx | 2 +- .../Strings/sv-SE/Resources.resx | 2 +- .../Strings/ar-SA/Resources.resx | 6 ++--- .../Strings/bg-BG/Resources.resx | 6 ++--- .../Strings/cs-CZ/Resources.resx | 6 ++--- .../Strings/da-DK/Resources.resx | 8 +++---- .../Strings/de-DE/Resources.resx | 6 ++--- .../Strings/el-GR/Resources.resx | 8 +++---- .../Strings/en-GB/Resources.resx | 4 ++-- .../Strings/en-US/Resources.resx | 4 ++-- .../Strings/es-ES/Resources.resx | 6 ++--- .../Strings/es-MX/Resources.resx | 6 ++--- .../Strings/et-EE/Resources.resx | 6 ++--- .../Strings/fi-FI/Resources.resx | 6 ++--- .../Strings/fr-CA/Resources.resx | 8 +++---- .../Strings/fr-FR/Resources.resx | 6 ++--- .../Strings/he-IL/Resources.resx | 6 ++--- .../Strings/hr-HR/Resources.resx | 6 ++--- .../Strings/hu-HU/Resources.resx | 6 ++--- .../Strings/it-IT/Resources.resx | 6 ++--- .../Strings/ja-JP/Resources.resx | 6 ++--- .../Strings/ko-KR/Resources.resx | 6 ++--- .../Strings/lt-LT/Resources.resx | 6 ++--- .../Strings/lv-LV/Resources.resx | 8 +++---- .../Strings/nb-NO/Resources.resx | 6 ++--- .../Strings/nl-NL/Resources.resx | 6 ++--- .../Strings/pl-PL/Resources.resx | 6 ++--- .../Strings/pt-BR/Resources.resx | 6 ++--- .../Strings/pt-PT/Resources.resx | 6 ++--- .../Strings/ro-RO/Resources.resx | 6 ++--- .../Strings/ru-RU/Resources.resx | 6 ++--- .../Strings/sk-SK/Resources.resx | 6 ++--- .../Strings/sl-SI/Resources.resx | 6 ++--- .../Strings/sr-Latn-RS/Resources.resx | 6 ++--- .../Strings/sv-SE/Resources.resx | 8 +++---- .../Strings/th-TH/Resources.resx | 6 ++--- .../Strings/tr-TR/Resources.resx | 6 ++--- .../Strings/uk-UA/Resources.resx | 6 ++--- .../Strings/zh-CN/Resources.resx | 6 ++--- .../Strings/zh-TW/Resources.resx | 6 ++--- src/Foundry/Strings/fr-CA/Resources.resw | 4 ++-- src/Foundry/Strings/lv-LV/Resources.resw | 2 +- src/Foundry/Strings/nl-NL/Resources.resw | 2 +- src/Foundry/Strings/sk-SK/Resources.resw | 24 +++++++++---------- src/Foundry/Strings/sv-SE/Resources.resw | 4 ++-- 47 files changed, 139 insertions(+), 139 deletions(-) diff --git a/src/Foundry.Connect/Strings/fr-CA/Resources.resx b/src/Foundry.Connect/Strings/fr-CA/Resources.resx index 9f931b49..f6016e78 100644 --- a/src/Foundry.Connect/Strings/fr-CA/Resources.resx +++ b/src/Foundry.Connect/Strings/fr-CA/Resources.resx @@ -87,7 +87,7 @@ Version: {0} - Configuration: {0} + Configuration : {0} Configuration: paramètres par défaut intégrés diff --git a/src/Foundry.Connect/Strings/lv-LV/Resources.resx b/src/Foundry.Connect/Strings/lv-LV/Resources.resx index f69b102d..8b386d7f 100644 --- a/src/Foundry.Connect/Strings/lv-LV/Resources.resx +++ b/src/Foundry.Connect/Strings/lv-LV/Resources.resx @@ -375,7 +375,7 @@ latviešu (Latvija) - Norwegian Bokmal (Norway) + norvēģu bukmols (Norvēģija) holandiešu (Nīderlande) diff --git a/src/Foundry.Connect/Strings/nl-NL/Resources.resx b/src/Foundry.Connect/Strings/nl-NL/Resources.resx index ac938697..8eefd1ca 100644 --- a/src/Foundry.Connect/Strings/nl-NL/Resources.resx +++ b/src/Foundry.Connect/Strings/nl-NL/Resources.resx @@ -303,7 +303,7 @@ Onderneming - {0} (machine) + {0} (computer) {0} (machine of gebruiker) diff --git a/src/Foundry.Connect/Strings/sv-SE/Resources.resx b/src/Foundry.Connect/Strings/sv-SE/Resources.resx index 0b530356..13d45892 100644 --- a/src/Foundry.Connect/Strings/sv-SE/Resources.resx +++ b/src/Foundry.Connect/Strings/sv-SE/Resources.resx @@ -168,7 +168,7 @@ Visa eller dölj Wi-Fi lösenord - Provisioned Wi-Fi + Provisionerat Wi-Fi Profil diff --git a/src/Foundry.Deploy/Strings/ar-SA/Resources.resx b/src/Foundry.Deploy/Strings/ar-SA/Resources.resx index 92dff1c2..19cba0d5 100644 --- a/src/Foundry.Deploy/Strings/ar-SA/Resources.resx +++ b/src/Foundry.Deploy/Strings/ar-SA/Resources.resx @@ -522,12 +522,12 @@ توفير استرداد نظام التشغيل جارٍ توفير استرداد نظام التشغيل... جارٍ إدخال حمولة استرداد نظام التشغيل... - Committing WinRE changes... + جارٍ حفظ تغييرات WinRE... جارٍ تسجيل قائمة تمهيد استرداد نظام التشغيل... تم تعطيل استرداد نظام التشغيل. يتم تخطي توفير استرداد نظام التشغيل في وضع الاسترداد. تم توفير استرداد نظام التشغيل. تم توفير استرداد نظام التشغيل (محاكاة). - استرداد Foundry - إعادة نشر Windows + استرداد Foundry OSD + إعادة نشر Windows للطوارئ diff --git a/src/Foundry.Deploy/Strings/bg-BG/Resources.resx b/src/Foundry.Deploy/Strings/bg-BG/Resources.resx index a8c060ac..92975a36 100644 --- a/src/Foundry.Deploy/Strings/bg-BG/Resources.resx +++ b/src/Foundry.Deploy/Strings/bg-BG/Resources.resx @@ -522,12 +522,12 @@ Осигуряване на възстановяване на ОС Осигуряване на възстановяване на ОС... Инжектиране на полезния товар за възстановяване на ОС... - Committing WinRE changes... + Запазване на промените в WinRE... Регистриране на менюто за стартиране на възстановяване на ОС... Възстановяването на ОС е деактивирано. Осигуряването на възстановяване на ОС се пропуска в режим на възстановяване. Осигурено възстановяване на ОС. Осигурено възстановяване на ОС (симулация). - Възстановяване Foundry - Преинсталиране Windows + Възстановяване Foundry OSD + Аварийно преинсталиране Windows diff --git a/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx b/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx index ea50e06c..0a1a5892 100644 --- a/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx +++ b/src/Foundry.Deploy/Strings/cs-CZ/Resources.resx @@ -522,12 +522,12 @@ Pokračovat v obnově OS? Připravit obnovení OS Provádění obnovy OS... Vkládání dat pro obnovu OS... - Committing WinRE changes... + Ukládání změn WinRE... Registrace spouštěcí nabídky OS Recovery... Obnovení OS je zakázáno. Poskytování obnovy OS je v režimu obnovy přeskočeno. Obnovení OS zajištěno. Bylo zajištěno obnovení OS (simulace). - Obnova Foundry - Znovu nasadit Windows + Obnova Foundry OSD + Nouzově znovu nasadit Windows diff --git a/src/Foundry.Deploy/Strings/da-DK/Resources.resx b/src/Foundry.Deploy/Strings/da-DK/Resources.resx index 037637ee..44961573 100644 --- a/src/Foundry.Deploy/Strings/da-DK/Resources.resx +++ b/src/Foundry.Deploy/Strings/da-DK/Resources.resx @@ -51,7 +51,7 @@ Interaktiv upload af hardwarehash Tilgængelig på dette medie: {0} Zero-touch upload af hardwarehash - Hardware hash upload status + Status for upload af hardware-hash Klar til at uploade hardware-hash Ikke klar til at uploade hardware-hash Uploadcertifikat er udløbet @@ -522,12 +522,12 @@ Vil du fortsætte med OS Recovery? Provision OS Gendannelse Klargøring af OS-gendannelse... Injicerer OS-gendannelsesnyttelast... - Committing WinRE changes... + Gemmer WinRE-ændringer... Registrerer OS Recovery boot-menu... OS-gendannelse er deaktiveret. Klargøring af OS-gendannelse springes over i gendannelsestilstand. OS-gendannelse klargjort. OS-gendannelse klargjort (simulering). - Foundry-gendannelse - Genudrul Windows + Foundry OSD-gendannelse + Nødgenudrulning af Windows diff --git a/src/Foundry.Deploy/Strings/de-DE/Resources.resx b/src/Foundry.Deploy/Strings/de-DE/Resources.resx index 877ab358..32575921 100644 --- a/src/Foundry.Deploy/Strings/de-DE/Resources.resx +++ b/src/Foundry.Deploy/Strings/de-DE/Resources.resx @@ -522,12 +522,12 @@ Mit der Betriebssystemwiederherstellung fortfahren? Bereitstellen der Betriebssystemwiederherstellung Betriebssystemwiederherstellung wird bereitgestellt... Nutzlast für die Betriebssystemwiederherstellung wird eingefügt... - Committing WinRE changes... + WinRE-Änderungen werden gespeichert... Das Bootmenü für die Betriebssystemwiederherstellung wird registriert... Die Betriebssystemwiederherstellung ist deaktiviert. Die Bereitstellung der Betriebssystemwiederherstellung wird im Wiederherstellungsmodus übersprungen. Betriebssystemwiederherstellung bereitgestellt. Betriebssystemwiederherstellung bereitgestellt (Simulation). - Foundry-Wiederherstellung - Windows neu bereitstellen + Foundry OSD-Wiederherstellung + Windows-Notfallbereitstellung diff --git a/src/Foundry.Deploy/Strings/el-GR/Resources.resx b/src/Foundry.Deploy/Strings/el-GR/Resources.resx index ffb2cfba..9f5d4268 100644 --- a/src/Foundry.Deploy/Strings/el-GR/Resources.resx +++ b/src/Foundry.Deploy/Strings/el-GR/Resources.resx @@ -31,7 +31,7 @@ Ανοίξτε το αρχείο καταγραφής 1. Στόχος 2. Λειτουργικό σύστημα - 3. Driver pack + 3. Πακέτο προγραμμάτων οδήγησης 4. Περίληψη Όνομα υπολογιστή 1-15 χαρακτήρες. A-Z, a-z, 0-9 και μόνο παύλα. @@ -522,12 +522,12 @@ Παροχή ανάκτησης λειτουργικού συστήματος Παροχή ανάκτησης λειτουργικού συστήματος... Έγχυση ωφέλιμου φορτίου ανάκτησης λειτουργικού συστήματος... - Committing WinRE changes... + Αποθήκευση αλλαγών WinRE... Εγγραφή του μενού εκκίνησης του OS Recovery... Η ανάκτηση λειτουργικού συστήματος είναι απενεργοποιημένη. Η παροχή ανάκτησης λειτουργικού συστήματος παραλείπεται στη λειτουργία ανάκτησης. Παρέχεται ανάκτηση λειτουργικού συστήματος. Παρέχεται ανάκτηση λειτουργικού συστήματος (προσομοίωση). - Ανάκτηση Foundry - Επανεγκατάσταση Windows + Ανάκτηση Foundry OSD + Επείγουσα ανάπτυξη Windows diff --git a/src/Foundry.Deploy/Strings/en-GB/Resources.resx b/src/Foundry.Deploy/Strings/en-GB/Resources.resx index 5ffb5ffa..08907663 100644 --- a/src/Foundry.Deploy/Strings/en-GB/Resources.resx +++ b/src/Foundry.Deploy/Strings/en-GB/Resources.resx @@ -528,6 +528,6 @@ Continue with OS Recovery? OS Recovery provisioning is skipped in recovery mode. OS Recovery provisioned. OS Recovery provisioned (simulation). - Foundry Recovery - Redeploy Windows + Foundry OSD Recovery + Emergency Windows redeploy diff --git a/src/Foundry.Deploy/Strings/en-US/Resources.resx b/src/Foundry.Deploy/Strings/en-US/Resources.resx index 71750f08..e3ab4056 100644 --- a/src/Foundry.Deploy/Strings/en-US/Resources.resx +++ b/src/Foundry.Deploy/Strings/en-US/Resources.resx @@ -528,6 +528,6 @@ Continue with OS Recovery? OS Recovery provisioning is skipped in recovery mode. OS Recovery provisioned. OS Recovery provisioned (simulation). - Foundry Recovery - Redeploy Windows + Foundry OSD Recovery + Emergency Windows redeploy diff --git a/src/Foundry.Deploy/Strings/es-ES/Resources.resx b/src/Foundry.Deploy/Strings/es-ES/Resources.resx index 75bdcc85..725c4896 100644 --- a/src/Foundry.Deploy/Strings/es-ES/Resources.resx +++ b/src/Foundry.Deploy/Strings/es-ES/Resources.resx @@ -522,12 +522,12 @@ Sistema operativo: {4} Aprovisionar recuperación del sistema operativo Aprovisionando recuperación del sistema operativo... Inyectando carga útil de recuperación del sistema operativo... - Committing WinRE changes... + Guardando cambios de WinRE... Registrando el menú de inicio de OS Recovery... La recuperación del sistema operativo está deshabilitada. El aprovisionamiento de OS Recovery se omite en el modo de recuperación. Recuperación del sistema operativo aprovisionada. Recuperación del sistema operativo aprovisionada (simulación). - Recuperación Foundry - Reinstalar Windows + Recuperación Foundry OSD + Reinstalación Windows urgente diff --git a/src/Foundry.Deploy/Strings/es-MX/Resources.resx b/src/Foundry.Deploy/Strings/es-MX/Resources.resx index 8c8c566d..460fe012 100644 --- a/src/Foundry.Deploy/Strings/es-MX/Resources.resx +++ b/src/Foundry.Deploy/Strings/es-MX/Resources.resx @@ -522,12 +522,12 @@ Sistema operativo: {4} Aprovisionar recuperación del sistema operativo Aprovisionando recuperación del sistema operativo... Inyectando carga útil de recuperación del sistema operativo... - Committing WinRE changes... + Guardando cambios de WinRE... Registrando el menú de inicio de OS Recovery... La recuperación del sistema operativo está deshabilitada. El aprovisionamiento de OS Recovery se omite en el modo de recuperación. Recuperación del sistema operativo aprovisionada. Recuperación del sistema operativo aprovisionada (simulación). - Recuperación Foundry - Reinstalar Windows + Recuperación Foundry OSD + Reinstalación Windows urgente diff --git a/src/Foundry.Deploy/Strings/et-EE/Resources.resx b/src/Foundry.Deploy/Strings/et-EE/Resources.resx index 3430696e..e51d1875 100644 --- a/src/Foundry.Deploy/Strings/et-EE/Resources.resx +++ b/src/Foundry.Deploy/Strings/et-EE/Resources.resx @@ -522,12 +522,12 @@ Kas jätkata OS-i taastamisega? OS-i taastamise pakkumine OS-i taastamise ettevalmistamine... OS-i taastamise kasuliku koormuse sisestamine... - Committing WinRE changes... + WinRE muudatuste salvestamine... OS-i taastamise alglaadimismenüü registreerimine... OS-i taastamine on keelatud. OS-i taastamise varustamine jäetakse taasterežiimis vahele. OS-i taastamine on ette nähtud. OS-i taastamine on ette nähtud (simulatsioon). - Foundry taaste - Juuruta Windows uuesti + Foundry OSD taaste + Windowsi avariijuurutus diff --git a/src/Foundry.Deploy/Strings/fi-FI/Resources.resx b/src/Foundry.Deploy/Strings/fi-FI/Resources.resx index a84faeaa..169a38f5 100644 --- a/src/Foundry.Deploy/Strings/fi-FI/Resources.resx +++ b/src/Foundry.Deploy/Strings/fi-FI/Resources.resx @@ -522,12 +522,12 @@ Jatketaanko käyttöjärjestelmän palautusta? Tarjoa käyttöjärjestelmän palautus Otetaan käyttöön käyttöjärjestelmän palautus... Lisätään käyttöjärjestelmän palautusta... - Committing WinRE changes... + Tallennetaan WinRE-muutoksia... Rekisteröidään OS Recovery -käynnistysvalikkoa... Käyttöjärjestelmän palautus on poistettu käytöstä. Käyttöjärjestelmän palautuksen hallinta ohitetaan palautustilassa. Käyttöjärjestelmän palautus varattu. Käyttöjärjestelmän palautus varusteltu (simulaatio). - Foundry-palautus - Asenna Windows uudelleen + Foundry OSD -palautus + Windowsin hätäasennus diff --git a/src/Foundry.Deploy/Strings/fr-CA/Resources.resx b/src/Foundry.Deploy/Strings/fr-CA/Resources.resx index 15e50157..c4fadc45 100644 --- a/src/Foundry.Deploy/Strings/fr-CA/Resources.resx +++ b/src/Foundry.Deploy/Strings/fr-CA/Resources.resx @@ -76,7 +76,7 @@ Langue Canal de licence Édition (cible) - Architecture: {0} + Architecture : {0} Chargement des catalogues... Catalogues chargés: {0} entrées du système d'exploitation, {1} packs de pilotes. Échec du chargement du catalogue: {0} @@ -522,12 +522,12 @@ Continuer avec la récupération du SE ? Provisionner la récupération du SE Provisionnement de la récupération du SE... Injection du payload de récupération du SE... - Committing WinRE changes... + Validation des modifications WinRE... Enregistrement du menu de démarrage de récupération du SE... La récupération du SE est désactivée. Le provisionnement de la récupération du SE est ignoré en mode récupération. Récupération du SE provisionnée. Récupération du SE provisionnée (simulation). - Récupération Foundry - Redéployer Windows + Récupération Foundry OSD + Redéployer Windows d’urgence diff --git a/src/Foundry.Deploy/Strings/fr-FR/Resources.resx b/src/Foundry.Deploy/Strings/fr-FR/Resources.resx index 96f5c0d7..fa825cd5 100644 --- a/src/Foundry.Deploy/Strings/fr-FR/Resources.resx +++ b/src/Foundry.Deploy/Strings/fr-FR/Resources.resx @@ -522,12 +522,12 @@ Continuer avec la récupération de l’OS ? Provisionner la récupération de l’OS Provisionnement de la récupération de l’OS... Injection du payload de récupération de l’OS... - Committing WinRE changes... + Validation des modifications WinRE... Enregistrement du menu de démarrage de récupération de l’OS... La récupération de l’OS est désactivée. Le provisionnement de la récupération de l’OS est ignoré en mode récupération. Récupération de l’OS provisionnée. Récupération de l’OS provisionnée (simulation). - Récupération Foundry - Redéployer Windows + Récupération Foundry OSD + Redéployer Windows d’urgence diff --git a/src/Foundry.Deploy/Strings/he-IL/Resources.resx b/src/Foundry.Deploy/Strings/he-IL/Resources.resx index 9a00d5ed..d8682971 100644 --- a/src/Foundry.Deploy/Strings/he-IL/Resources.resx +++ b/src/Foundry.Deploy/Strings/he-IL/Resources.resx @@ -522,12 +522,12 @@ ErrorCode=0x80070005 שחזור מערכת הפעלה אספקה הקצאת שחזור מערכת ההפעלה... מזריקים מטען שחזור מערכת ההפעלה... - Committing WinRE changes... + שומר שינויים ב-WinRE... רושם את תפריט האתחול של שחזור מערכת ההפעלה... שחזור מערכת ההפעלה מושבת. דילוג על הקצאת שחזור מערכת ההפעלה במצב שחזור. שחזור מערכת הפעלה הוקצה. התאוששות מערכת הפעלה הוגדרה (סימולציה). - שחזור Foundry - פריסה מחדש של Windows + שחזור Foundry OSD + פריסת Windows בחירום diff --git a/src/Foundry.Deploy/Strings/hr-HR/Resources.resx b/src/Foundry.Deploy/Strings/hr-HR/Resources.resx index 2e656fca..827172ec 100644 --- a/src/Foundry.Deploy/Strings/hr-HR/Resources.resx +++ b/src/Foundry.Deploy/Strings/hr-HR/Resources.resx @@ -522,12 +522,12 @@ Nastaviti s oporavkom OS-a? Pružanje oporavka OS-a Omogućavanje oporavka OS-a... Ubacivanje nosivosti oporavka OS-a... - Committing WinRE changes... + Spremanje WinRE promjena... Registriranje izbornika za pokretanje OS Recovery... Oporavak OS-a je onemogućen. Omogućavanje oporavka OS-a preskočeno je u načinu oporavka. Omogućen oporavak OS-a. Osiguran oporavak OS-a (simulacija). - Foundry oporavak - Ponovno uvedi Windows + Foundry OSD oporavak + Hitno ponovno uvođenje Windowsa diff --git a/src/Foundry.Deploy/Strings/hu-HU/Resources.resx b/src/Foundry.Deploy/Strings/hu-HU/Resources.resx index a7b77829..ed12efef 100644 --- a/src/Foundry.Deploy/Strings/hu-HU/Resources.resx +++ b/src/Foundry.Deploy/Strings/hu-HU/Resources.resx @@ -522,12 +522,12 @@ Folytatja az OS helyreállítást? Az operációs rendszer helyreállításának biztosítása Az operációs rendszer helyreállításának kiépítése... OS Recovery hasznos terhelés beadása... - Committing WinRE changes... + WinRE-módosítások mentése... Az OS Recovery rendszerindító menü regisztrálása... Az operációs rendszer helyreállítása le van tiltva. Az OS-helyreállítás kiépítése helyreállítási módban kimarad. Az operációs rendszer helyreállítása biztosított. Az operációs rendszer helyreállítása biztosított (szimuláció). - Foundry-helyreállítás - Windows újratelepítése + Foundry OSD-helyreállítás + Windows vészhelyzeti újratelepítése diff --git a/src/Foundry.Deploy/Strings/it-IT/Resources.resx b/src/Foundry.Deploy/Strings/it-IT/Resources.resx index 8b50e38b..662aa4f3 100644 --- a/src/Foundry.Deploy/Strings/it-IT/Resources.resx +++ b/src/Foundry.Deploy/Strings/it-IT/Resources.resx @@ -522,12 +522,12 @@ Continuare con il ripristino del sistema operativo? Fornire il ripristino del sistema operativo Provisioning del ripristino del sistema operativo... Inserimento del payload di ripristino del sistema operativo in corso... - Committing WinRE changes... + Salvataggio delle modifiche WinRE... Registrazione del menu di avvio di ripristino del sistema operativo in corso... Il ripristino del sistema operativo è disabilitato. Il provisioning del ripristino del sistema operativo viene ignorato in modalità di ripristino. Effettuato il provisioning del ripristino del sistema operativo. Provisioning del ripristino del sistema operativo (simulazione). - Ripristino Foundry - Reinstalla Windows + Ripristino Foundry OSD + Reinstallazione urgente Windows diff --git a/src/Foundry.Deploy/Strings/ja-JP/Resources.resx b/src/Foundry.Deploy/Strings/ja-JP/Resources.resx index d32306cd..0dc56b3c 100644 --- a/src/Foundry.Deploy/Strings/ja-JP/Resources.resx +++ b/src/Foundry.Deploy/Strings/ja-JP/Resources.resx @@ -522,12 +522,12 @@ OS リカバリを続行しますか? OS リカバリのプロビジョニング OS リカバリをプロビジョニングしています... OS リカバリ ペイロードを挿入しています... - Committing WinRE changes... + WinRE の変更を保存しています... OS リカバリ ブート メニューを登録しています... OS リカバリは無効になっています。 OS リカバリ プロビジョニングはリカバリ モードではスキップされます。 OS リカバリがプロビジョニングされました。 OS リカバリがプロビジョニングされました (シミュレーション)。 - Foundry 回復 - Windows を再展開 + Foundry OSD 回復 + 緊急 Windows 再展開 diff --git a/src/Foundry.Deploy/Strings/ko-KR/Resources.resx b/src/Foundry.Deploy/Strings/ko-KR/Resources.resx index 9f9d9084..fd108c69 100644 --- a/src/Foundry.Deploy/Strings/ko-KR/Resources.resx +++ b/src/Foundry.Deploy/Strings/ko-KR/Resources.resx @@ -522,12 +522,12 @@ OS 복구를 계속하시겠습니까? OS 복구 프로비저닝 OS 복구 프로비저닝 중... OS 복구 페이로드를 삽입하는 중... - Committing WinRE changes... + WinRE 변경 내용을 저장하는 중... OS 복구 부팅 메뉴 등록 중... OS 복구가 비활성화되었습니다. 복구 모드에서는 OS 복구 프로비저닝을 건너뜁니다. OS 복구가 프로비저닝되었습니다. OS 복구 프로비저닝됨(시뮬레이션) - Foundry 복구 - Windows 재배포 + Foundry OSD 복구 + 긴급 Windows 재배포 diff --git a/src/Foundry.Deploy/Strings/lt-LT/Resources.resx b/src/Foundry.Deploy/Strings/lt-LT/Resources.resx index 2863f198..bfd1be4a 100644 --- a/src/Foundry.Deploy/Strings/lt-LT/Resources.resx +++ b/src/Foundry.Deploy/Strings/lt-LT/Resources.resx @@ -522,12 +522,12 @@ Tęsti OS atkūrimą? Pateikite OS atkūrimą Teikiamas OS atkūrimas... Įvedama OS atkūrimo naudingoji apkrova... - Committing WinRE changes... + Įrašomi WinRE pakeitimai... Registruojamas OS atkūrimo įkrovos meniu... OS atkūrimas išjungtas. Atkūrimo režimu OS atkūrimo aprūpinimas praleidžiamas. Suteiktas OS atkūrimas. OS atkūrimas numatytas (modeliavimas). - Foundry atkūrimas - Iš naujo diegti Windows + Foundry OSD atkūrimas + Avarinis Windows diegimas diff --git a/src/Foundry.Deploy/Strings/lv-LV/Resources.resx b/src/Foundry.Deploy/Strings/lv-LV/Resources.resx index 9598649c..857223af 100644 --- a/src/Foundry.Deploy/Strings/lv-LV/Resources.resx +++ b/src/Foundry.Deploy/Strings/lv-LV/Resources.resx @@ -444,7 +444,7 @@ Vai turpināt izvietošanu? latviešu (Latvija) - Norwegian Bokmal (Norway) + norvēģu bukmols (Norvēģija) holandiešu (Nīderlande) @@ -522,12 +522,12 @@ Vai turpināt ar OS atkopšanu? Nodrošiniet OS atkopšanu Notiek OS atkopšanas nodrošināšana... Notiek OS atkopšanas derīgās slodzes ievadīšana... - Committing WinRE changes... + Tiek saglabātas WinRE izmaiņas... Notiek OS atkopšanas sāknēšanas izvēlnes reģistrācija... OS atkopšana ir atspējota. Atkopšanas režīmā OS atkopšanas nodrošināšana tiek izlaista. Nodrošināta OS atkopšana. Nodrošināta OS atkopšana (simulācija). - Foundry atkopšana - Pārizvietot Windows + Foundry OSD atkopšana + Windows ārkārtas pārizvietošana diff --git a/src/Foundry.Deploy/Strings/nb-NO/Resources.resx b/src/Foundry.Deploy/Strings/nb-NO/Resources.resx index 54e5199b..0aa4e553 100644 --- a/src/Foundry.Deploy/Strings/nb-NO/Resources.resx +++ b/src/Foundry.Deploy/Strings/nb-NO/Resources.resx @@ -522,12 +522,12 @@ Vil du fortsette med OS-gjenoppretting? Klargjør OS-gjenoppretting Klargjør OS-gjenoppretting... Injiserer nyttelast for OS-gjenoppretting... - Committing WinRE changes... + Lagrer WinRE-endringer... Registrerer OS Recovery oppstartsmeny... OS-gjenoppretting er deaktivert. Klargjøring av OS-gjenoppretting hoppes over i gjenopprettingsmodus. OS-gjenoppretting klargjort. OS-gjenoppretting klargjort (simulering). - Foundry-gjenoppretting - Rull ut Windows på nytt + Foundry OSD-gjenoppretting + Nødutrulling av Windows diff --git a/src/Foundry.Deploy/Strings/nl-NL/Resources.resx b/src/Foundry.Deploy/Strings/nl-NL/Resources.resx index 66d41373..7c1fa6ea 100644 --- a/src/Foundry.Deploy/Strings/nl-NL/Resources.resx +++ b/src/Foundry.Deploy/Strings/nl-NL/Resources.resx @@ -522,12 +522,12 @@ Doorgaan met OS-herstel? OS-herstel inrichten Besturingssysteemherstel inrichten... Bezig met injecteren van de OS Recovery-payload... - Committing WinRE changes... + WinRE-wijzigingen worden opgeslagen... Registreren van het opstartmenu van OS Recovery... Besturingssysteemherstel is uitgeschakeld. Het inrichten van OS Recovery wordt overgeslagen in de herstelmodus. OS-herstel ingericht. OS Recovery ingericht (simulatie). - Foundry-herstel - Windows opnieuw uitrollen + Foundry OSD-herstel + Windows nooduitrol diff --git a/src/Foundry.Deploy/Strings/pl-PL/Resources.resx b/src/Foundry.Deploy/Strings/pl-PL/Resources.resx index 8bad25ff..c8687543 100644 --- a/src/Foundry.Deploy/Strings/pl-PL/Resources.resx +++ b/src/Foundry.Deploy/Strings/pl-PL/Resources.resx @@ -522,12 +522,12 @@ Kontynuować odzyskiwanie systemu operacyjnego? Zapewnij odzyskiwanie systemu operacyjnego Udostępnianie odzyskiwania systemu operacyjnego... Wstrzykiwanie ładunku odzyskiwania systemu operacyjnego... - Committing WinRE changes... + Zapisywanie zmian WinRE... Rejestrowanie menu startowego odzyskiwania systemu operacyjnego... Odzyskiwanie systemu operacyjnego jest wyłączone. Udostępnianie systemu operacyjnego OS Recovery jest pomijane w trybie odzyskiwania. Zapewnione odzyskiwanie systemu operacyjnego. Zapewnione odzyskiwanie systemu operacyjnego (symulacja). - Odzyskiwanie Foundry - Wdróż Windows ponownie + Odzyskiwanie Foundry OSD + Awaryjne wdrożenie Windows diff --git a/src/Foundry.Deploy/Strings/pt-BR/Resources.resx b/src/Foundry.Deploy/Strings/pt-BR/Resources.resx index 31f8c3ae..ebfce5ad 100644 --- a/src/Foundry.Deploy/Strings/pt-BR/Resources.resx +++ b/src/Foundry.Deploy/Strings/pt-BR/Resources.resx @@ -522,12 +522,12 @@ Continuar com a recuperação do sistema operacional? Provisionar recuperação de sistema operacional Provisionando recuperação do sistema operacional... Injetando carga útil de recuperação do sistema operacional... - Committing WinRE changes... + Salvando alterações do WinRE... Registrando o menu de inicialização do OS Recovery... A recuperação do sistema operacional está desativada. O provisionamento do OS Recovery é ignorado no modo de recuperação. Recuperação do sistema operacional provisionada. Recuperação de SO provisionada (simulação). - Recuperação Foundry - Reimplantar Windows + Recuperação Foundry OSD + Reimplantação Windows emergencial diff --git a/src/Foundry.Deploy/Strings/pt-PT/Resources.resx b/src/Foundry.Deploy/Strings/pt-PT/Resources.resx index 4c685192..c942297b 100644 --- a/src/Foundry.Deploy/Strings/pt-PT/Resources.resx +++ b/src/Foundry.Deploy/Strings/pt-PT/Resources.resx @@ -522,12 +522,12 @@ Continuar com a recuperação do sistema operacional? Provisionar recuperação de sistema operacional Provisionando recuperação do sistema operacional... Injetando carga útil de recuperação do sistema operacional... - Committing WinRE changes... + A guardar alterações do WinRE... Registrando o menu de inicialização do OS Recovery... A recuperação do sistema operacional está desativada. O provisionamento do OS Recovery é ignorado no modo de recuperação. Recuperação do sistema operacional provisionada. Recuperação de SO provisionada (simulação). - Recuperação Foundry - Reimplementar Windows + Recuperação Foundry OSD + Reimplementação Windows urgente diff --git a/src/Foundry.Deploy/Strings/ro-RO/Resources.resx b/src/Foundry.Deploy/Strings/ro-RO/Resources.resx index 026c3210..2761b48c 100644 --- a/src/Foundry.Deploy/Strings/ro-RO/Resources.resx +++ b/src/Foundry.Deploy/Strings/ro-RO/Resources.resx @@ -522,12 +522,12 @@ Continuați cu OS Recovery? Asigurați recuperarea sistemului de operare Se asigură recuperarea sistemului de operare... Se injectează sarcina utilă de recuperare a sistemului de operare... - Committing WinRE changes... + Se salvează modificările WinRE... Se înregistrează meniul de pornire OS Recovery... Recuperarea sistemului de operare este dezactivată. Aprovizionarea OS Recovery este omisă în modul de recuperare. Recuperarea sistemului de operare a fost asigurată. Recuperare OS asigurată (simulare). - Recuperare Foundry - Redeplasați Windows + Recuperare Foundry OSD + Redeploy Windows de urgență diff --git a/src/Foundry.Deploy/Strings/ru-RU/Resources.resx b/src/Foundry.Deploy/Strings/ru-RU/Resources.resx index 691598f2..eb849d22 100644 --- a/src/Foundry.Deploy/Strings/ru-RU/Resources.resx +++ b/src/Foundry.Deploy/Strings/ru-RU/Resources.resx @@ -522,12 +522,12 @@ Обеспечение восстановления ОС Обеспечение восстановления ОС... Внедрение полезных данных восстановления ОС... - Committing WinRE changes... + Сохранение изменений WinRE... Регистрация меню загрузки OS Recovery... Восстановление ОС отключено. В режиме восстановления подготовка ОС для восстановления пропускается. Предусмотрено восстановление ОС. Предусмотрено восстановление ОС (симуляция). - Восстановление Foundry - Развернуть Windows заново + Восстановление Foundry OSD + Аварийное развертывание Windows diff --git a/src/Foundry.Deploy/Strings/sk-SK/Resources.resx b/src/Foundry.Deploy/Strings/sk-SK/Resources.resx index f0753975..6e766167 100644 --- a/src/Foundry.Deploy/Strings/sk-SK/Resources.resx +++ b/src/Foundry.Deploy/Strings/sk-SK/Resources.resx @@ -522,12 +522,12 @@ Pokračovať v obnove OS? Poskytovanie obnovy OS Poskytovanie obnovy OS... Vkladá sa užitočné zaťaženie na obnovenie OS... - Committing WinRE changes... + Ukladajú sa zmeny WinRE... Registrácia zavádzacej ponuky obnovy OS... Obnovenie OS je vypnuté. Poskytovanie obnovy OS sa v režime obnovenia preskočí. Zabezpečené obnovenie OS. Zabezpečené obnovenie OS (simulácia). - Obnova Foundry - Znovu nasadiť Windows + Obnova Foundry OSD + Núdzové nasadenie Windows diff --git a/src/Foundry.Deploy/Strings/sl-SI/Resources.resx b/src/Foundry.Deploy/Strings/sl-SI/Resources.resx index 9429084d..89c7ec00 100644 --- a/src/Foundry.Deploy/Strings/sl-SI/Resources.resx +++ b/src/Foundry.Deploy/Strings/sl-SI/Resources.resx @@ -522,12 +522,12 @@ Nadaljevati z obnovitvijo OS? Zagotavljanje obnovitve OS Omogočanje obnovitve OS ... Vstavljanje tovora za obnovitev OS ... - Committing WinRE changes... + Shranjevanje sprememb WinRE... Registracija zagonskega menija za obnovitev OS ... Obnovitev OS je onemogočena. Omogočanje obnovitve OS je v obnovitvenem načinu preskočeno. Zagotovljena obnovitev OS. Zagotovljena obnovitev OS (simulacija). - Obnovitev Foundry - Znova uvedi Windows + Obnovitev Foundry OSD + Nujna uvedba Windows diff --git a/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx b/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx index d34eb9a0..457866dc 100644 --- a/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx +++ b/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx @@ -522,12 +522,12 @@ Nastaviti sa oporavkom OS-a? Pripremi oporavak OS-a Priprema oporavka OS-a... Ubacivanje sadržaja oporavka OS-a... - Committing WinRE changes... + Čuvanje WinRE promena... Registrovanje menija za pokretanje oporavka OS-a... Oporavak OS-a je onemogućen. Priprema oporavka OS-a se preskače u režimu oporavka. Oporavak OS-a je pripremljen. Oporavak OS-a je pripremljen (simulacija). - Foundry oporavak - Ponovo uvedi Windows + Foundry OSD oporavak + Hitno ponovno uvođenje Windowsa diff --git a/src/Foundry.Deploy/Strings/sv-SE/Resources.resx b/src/Foundry.Deploy/Strings/sv-SE/Resources.resx index 1ee44347..5d71174b 100644 --- a/src/Foundry.Deploy/Strings/sv-SE/Resources.resx +++ b/src/Foundry.Deploy/Strings/sv-SE/Resources.resx @@ -207,7 +207,7 @@ Fortsätt med implementeringen? Ladda ner firmwareuppdatering Applicera firmwareuppdatering Försegla återställningspartition - Provision Autopilot + Provisionera Autopilot Slutför distributionen och skriv loggar Samlar in implementeringsvariabler... Samlar in implementeringskontext... @@ -522,12 +522,12 @@ Fortsätt med OS Recovery? Provisionera OS-återställning Provisionerar OS-återställning... Injicerar nyttolast för OS-återställning... - Committing WinRE changes... + Sparar WinRE-ändringar... Registrerar OS Recovery startmeny... OS-återställning är inaktiverad. Provisionering av OS-återställning hoppas över i återställningsläge. OS-återställning tillhandahållen. OS-återställning tillhandahållen (simulering). - Foundry-återställning - Distribuera Windows igen + Foundry OSD-återställning + Nöddistribuera Windows diff --git a/src/Foundry.Deploy/Strings/th-TH/Resources.resx b/src/Foundry.Deploy/Strings/th-TH/Resources.resx index 5f09950e..4a77ce3e 100644 --- a/src/Foundry.Deploy/Strings/th-TH/Resources.resx +++ b/src/Foundry.Deploy/Strings/th-TH/Resources.resx @@ -522,12 +522,12 @@ จัดเตรียมการกู้คืนระบบปฏิบัติการ กำลังจัดเตรียมการกู้คืนระบบปฏิบัติการ... กำลังเพิ่มเพย์โหลดการกู้คืน OS... - Committing WinRE changes... + กำลังบันทึกการเปลี่ยนแปลง WinRE... กำลังลงทะเบียนเมนูบูต OS Recovery... การกู้คืนระบบปฏิบัติการถูกปิดใช้งาน การจัดสรรการกู้คืนระบบปฏิบัติการถูกข้ามไปในโหมดการกู้คืน จัดเตรียมการกู้คืนระบบปฏิบัติการแล้ว จัดเตรียมการกู้คืน OS (การจำลอง) - กู้คืน Foundry - ปรับใช้ Windows ใหม่ + กู้คืน Foundry OSD + ปรับใช้ Windows ฉุกเฉิน diff --git a/src/Foundry.Deploy/Strings/tr-TR/Resources.resx b/src/Foundry.Deploy/Strings/tr-TR/Resources.resx index 4c8c19a7..4b59e15c 100644 --- a/src/Foundry.Deploy/Strings/tr-TR/Resources.resx +++ b/src/Foundry.Deploy/Strings/tr-TR/Resources.resx @@ -522,12 +522,12 @@ Boyut: {3} İşletim Sistemi Kurtarmanın Sağlanması İşletim Sistemi Kurtarma Sağlanıyor... İşletim Sistemi Kurtarma yükü enjekte ediliyor... - Committing WinRE changes... + WinRE değişiklikleri kaydediliyor... İşletim Sistemi Kurtarma önyükleme menüsü kaydediliyor... İşletim Sistemi Kurtarma devre dışı. İşletim Sistemi Kurtarma provizyonu, kurtarma modunda atlanır. İşletim Sistemi Kurtarma sağlandı. İşletim Sistemi Kurtarmanın temel hazırlığı yapıldı (simülasyon). - Foundry Kurtarma - Windows'u yeniden dağıt + Foundry OSD Kurtarma + Acil Windows yeniden dağıtımı diff --git a/src/Foundry.Deploy/Strings/uk-UA/Resources.resx b/src/Foundry.Deploy/Strings/uk-UA/Resources.resx index 9529e330..5f980cbb 100644 --- a/src/Foundry.Deploy/Strings/uk-UA/Resources.resx +++ b/src/Foundry.Deploy/Strings/uk-UA/Resources.resx @@ -522,12 +522,12 @@ ErrorCode=0x80070005 Надання відновлення ОС Ініціалізація відновлення ОС... Впровадження корисного навантаження відновлення ОС... - Committing WinRE changes... + Збереження змін WinRE... Реєстрація меню завантаження відновлення ОС... Відновлення ОС вимкнено. Ініціалізація відновлення ОС пропускається в режимі відновлення. Забезпечено відновлення ОС. Забезпечено відновлення ОС (симуляція). - Відновлення Foundry - Розгорнути Windows знову + Відновлення Foundry OSD + Аварійне розгортання Windows diff --git a/src/Foundry.Deploy/Strings/zh-CN/Resources.resx b/src/Foundry.Deploy/Strings/zh-CN/Resources.resx index 2c859c33..99fe1e81 100644 --- a/src/Foundry.Deploy/Strings/zh-CN/Resources.resx +++ b/src/Foundry.Deploy/Strings/zh-CN/Resources.resx @@ -522,12 +522,12 @@ 配置操作系统恢复 配置操作系统恢复... 正在注入操作系统恢复负载... - Committing WinRE changes... + 正在保存 WinRE 更改... 正在注册操作系统恢复启动菜单... 操作系统恢复已禁用。 在恢复模式下会跳过操作系统恢复配置。 已配置操作系统恢复。 已配置操作系统恢复(模拟)。 - Foundry 恢复 - 重新部署 Windows + Foundry OSD 恢复 + 紧急重新部署 Windows diff --git a/src/Foundry.Deploy/Strings/zh-TW/Resources.resx b/src/Foundry.Deploy/Strings/zh-TW/Resources.resx index da370bb8..15735e96 100644 --- a/src/Foundry.Deploy/Strings/zh-TW/Resources.resx +++ b/src/Foundry.Deploy/Strings/zh-TW/Resources.resx @@ -522,12 +522,12 @@ 配置作業系統恢復 配置作業系統恢復... 正在註入作業系統恢復負載... - Committing WinRE changes... + 正在儲存 WinRE 變更... 正在註冊作業系統恢復啟動選單... 作業系統恢復已停用。 在復原模式下會跳過作業系統恢復配置。 已配置作業系統恢復。 已配置作業系統恢復(模擬)。 - Foundry 復原 - 重新部署 Windows + Foundry OSD 復原 + 緊急重新部署 Windows diff --git a/src/Foundry/Strings/fr-CA/Resources.resw b/src/Foundry/Strings/fr-CA/Resources.resw index d15b6d7b..162ec59e 100644 --- a/src/Foundry/Strings/fr-CA/Resources.resw +++ b/src/Foundry/Strings/fr-CA/Resources.resw @@ -28,7 +28,7 @@ Chargement des contributeurs... - Contributions: {0} + Contributions : {0} Foundry OSD est une application moderne permettant de créer des supports de déploiement Windows optimisés par WinPE. @@ -1522,7 +1522,7 @@ Norme WinPE - WinRE Wi-Fi image + Image WinRE Wi-Fi Dell diff --git a/src/Foundry/Strings/lv-LV/Resources.resw b/src/Foundry/Strings/lv-LV/Resources.resw index 5cb66a6c..c7240c92 100644 --- a/src/Foundry/Strings/lv-LV/Resources.resw +++ b/src/Foundry/Strings/lv-LV/Resources.resw @@ -1966,7 +1966,7 @@ latviešu (Latvija) - Norwegian Bokmal (Norway) + norvēģu bukmols (Norvēģija) holandiešu (Nīderlande) diff --git a/src/Foundry/Strings/nl-NL/Resources.resw b/src/Foundry/Strings/nl-NL/Resources.resw index 7a251627..57f89e3d 100644 --- a/src/Foundry/Strings/nl-NL/Resources.resw +++ b/src/Foundry/Strings/nl-NL/Resources.resw @@ -1711,7 +1711,7 @@ Updaten en opnieuw opstarten - Update Foundry OSD + Foundry OSD bijwerken De update downloaden. Foundry OSD zal opnieuw opstarten als het klaar is. diff --git a/src/Foundry/Strings/sk-SK/Resources.resw b/src/Foundry/Strings/sk-SK/Resources.resw index 086d36c8..dfc124a7 100644 --- a/src/Foundry/Strings/sk-SK/Resources.resw +++ b/src/Foundry/Strings/sk-SK/Resources.resw @@ -1441,7 +1441,7 @@ Nahrávanie hardvérového hash je povolené, ale vybraný PFX nezodpovedá registračnému certifikátu aplikácie. - Hardware hash upload is enabled but the selected certificate thumbprint is missing. + Nahrávanie hardvérového hashu je povolené, ale chýba odtlačok vybratého certifikátu. Nahrávanie hardvérového hash je povolené, ale chýba vybratý certifikát. @@ -1450,22 +1450,22 @@ Nahrávanie hardvérového hash je povolené, ale platnosť vybratého certifikátu vypršala. Pred vytvorením zavádzacieho média vyberte platný certifikát. - Hardware hash upload is enabled but no boot media PFX is selected. + Nahrávanie hardvérového hashu je povolené, ale nie je vybratý žiadny PFX pre zavádzacie médium. Odovzdávanie hardvérového hash je povolené, ale chýba heslo PFX zavádzacieho média. - Hardware hash upload is enabled but the boot media PFX has not been validated. + Nahrávanie hardvérového hashu je povolené, ale PFX zavádzacieho média nebol overený. - Hardware hash upload is enabled but the selected PFX does not match the active certificate. + Nahrávanie hardvérového hashu je povolené, ale vybratý PFX sa nezhoduje s aktívnym certifikátom. Odovzdávanie hardvérového hash je povolené, ale nebolo možné overiť platnosť spúšťacieho média PFX. - Hardware hash upload is enabled but the selected boot media PFX has expired. + Nahrávanie hardvérového hashu je povolené, ale vybratému PFX zavádzacieho média vypršala platnosť. Doplnok ADK alebo WinPE nie je pripravený. @@ -1477,7 +1477,7 @@ WinPE jazyk zavádzania nie je vybratý. - Selected WinPE boot language is not available for the selected architecture. + Vybraný jazyk zavádzania WinPE nie je dostupný pre vybranú architektúru. Konfigurácia siete nie je pripravená. @@ -1507,7 +1507,7 @@ Vlastný priečinok ovládača neexistuje. - Custom driver folder does not contain .inf files. + Vlastný priečinok ovládačov neobsahuje súbory .inf. Vytváranie médií nie je v tejto zostave povolené. @@ -1639,31 +1639,31 @@ Nastavenia - Manage startup, language, diagnostics, and application behavior. + Spravujte spustenie, jazyk, diagnostiku a správanie aplikácie. generál - Send anonymous usage telemetry to help improve Foundry. No names, secrets, SSIDs, IP addresses, file paths, disk identifiers, computer names, Autopilot profile names, or hardware identifiers are sent. + Odosielajte anonymnú telemetriu používania, ktorá pomáha zlepšovať Foundry. Neodosielajú sa žiadne mená, tajomstvá, SSID, IP adresy, cesty k súborom, identifikátory diskov, názvy počítačov, názvy profilov Autopilot ani identifikátory hardvéru. Povoliť telemetriu - Change theme, backdrop, and accent color preferences. + Zmeňte motív, pozadie a predvoľby farby zvýraznenia. Vzhľad - Check for Foundry OSD updates and review release notes. + Vyhľadajte aktualizácie Foundry OSD a pozrite si poznámky k vydaniu. Aktualizovať aplikáciu - Open Windows color settings to change the accent color. + Otvorte nastavenia farieb systému Windows a zmeňte farbu zvýraznenia. Farba akcentu diff --git a/src/Foundry/Strings/sv-SE/Resources.resw b/src/Foundry/Strings/sv-SE/Resources.resw index 9a97eae6..2dd1b2cf 100644 --- a/src/Foundry/Strings/sv-SE/Resources.resw +++ b/src/Foundry/Strings/sv-SE/Resources.resw @@ -64,7 +64,7 @@ Laddar versionsinformation... - Release notes + Versionsinformation Förvar @@ -94,7 +94,7 @@ Uppdatera och starta om - Release notes + Versionsinformation Uppdatera källa From 1440bbb58ce53abae436a1a17272d61113b06ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Tue, 16 Jun 2026 00:13:55 +0200 Subject: [PATCH 10/11] fix(deploy): provision curl for os recovery --- scripts/Test-FoundryOsRecoveryWinRe.ps1 | 40 ++++++++++++++----- ...RecoveryPayloadProvisioningServiceTests.cs | 11 +++++ .../OsRecoveryPayloadProvisioningOptions.cs | 1 + .../OsRecoveryPayloadProvisioningService.cs | 13 ++++++ .../ProvisionOsRecoveryStepTests.cs | 1 + .../Steps/ProvisionOsRecoveryStep.cs | 6 +++ 6 files changed, 62 insertions(+), 10 deletions(-) diff --git a/scripts/Test-FoundryOsRecoveryWinRe.ps1 b/scripts/Test-FoundryOsRecoveryWinRe.ps1 index a25e9bc1..9c43cca1 100644 --- a/scripts/Test-FoundryOsRecoveryWinRe.ps1 +++ b/scripts/Test-FoundryOsRecoveryWinRe.ps1 @@ -26,7 +26,10 @@ param( [string]$WinReConfigFile = 'WinREConfig.xml', [Parameter()] - [string]$BootstrapScript = 'FoundryBootstrap.ps1', + [string]$CurlExecutablePath = 'Windows\System32\curl.exe', + + [Parameter()] + [string]$SevenZipExecutablePattern = 'Foundry\\Tools\\7zip\\[^\\]+\\7za\.exe$', [Parameter()] [string[]]$ExcludedConfigPatterns = @( @@ -183,6 +186,14 @@ $launcher = Test-ExistsAny -Root $root -Candidates $LauncherCandidates if ($launcher) { $checkResults.Add("PASS Launcher: $launcher") $successCount++ + + $launcherContent = Get-Content -LiteralPath $launcher -Raw + if ($launcherContent -match '(?i)powershell|FoundryBootstrap\.ps1') { + $errors.Add('FAIL Launcher still references PowerShell or FoundryBootstrap.ps1') + } else { + $checkResults.Add('PASS Launcher is CMD-only') + $successCount++ + } } else { $errors.Add('FAIL Missing WinRE recovery launcher') } @@ -196,17 +207,26 @@ if ($winReConfigPath) { $errors.Add('FAIL Missing WinREConfig.xml') } -# Bootstrap script -$bootstrapPath = Test-ExistsAny -Root $root -Candidates @( - $BootstrapScript, - "Windows\\System32\\$BootstrapScript", - "Windows\\System32\\WinPe\\$BootstrapScript" +# Obsolete recovery bootstrap artifacts +$obsoleteBootstrapPath = Test-ExistsAny -Root $root -Candidates @( + 'FoundryBootstrap.ps1', + 'Windows\System32\FoundryBootstrap.ps1', + 'Windows\System32\WinPe\FoundryBootstrap.ps1' ) -Recurse -if ($bootstrapPath) { - $checkResults.Add("PASS Bootstrap: $bootstrapPath") +if ($obsoleteBootstrapPath) { + $errors.Add("FAIL Obsolete FoundryBootstrap.ps1 artifact present: $obsoleteBootstrapPath") +} else { + $checkResults.Add('PASS Obsolete FoundryBootstrap.ps1 absent') + $successCount++ +} + +# curl.exe +$curlPath = Join-Path -Path $root -ChildPath $CurlExecutablePath +if (Test-Path -LiteralPath $curlPath) { + $checkResults.Add("PASS curl.exe: $curlPath") $successCount++ } else { - $errors.Add('FAIL Missing FoundryBootstrap') + $errors.Add("FAIL Missing curl.exe: $curlPath") } # Foundry.Connect executable @@ -221,7 +241,7 @@ if ($connectMatches) { # 7-Zip runtime $sevenZipMatches = Get-ChildItem -Path $root -Recurse -File -ErrorAction SilentlyContinue | - Where-Object { $_.FullName -match 'Foundry\\Tools\\7zip\\[^\\]+\\7za\.exe$' } + Where-Object { $_.FullName -match $SevenZipExecutablePattern } $sevenZipLicense = Join-Path -Path $root -ChildPath 'Foundry\Tools\7zip\License.txt' $sevenZipReadme = Join-Path -Path $root -ChildPath 'Foundry\Tools\7zip\readme.txt' if ($sevenZipMatches -and (Test-Path -LiteralPath $sevenZipLicense) -and (Test-Path -LiteralPath $sevenZipReadme)) { diff --git a/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs b/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs index d84fdfd9..7870338d 100644 --- a/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs +++ b/src/Foundry.Core.Tests/WinPe/OsRecoveryPayloadProvisioningServiceTests.cs @@ -28,6 +28,7 @@ public async Task ProvisionAsync_StagesRequiredFilesAndExcludesWinPeOnlyArtifact FoundryConnectConfigurationJson = "{\"schemaVersion\":1}", DeployConfigurationJson = "{\"schemaVersion\":2}", IanaWindowsTimeZoneMapJson = "{\"zones\":[]}", + CurlExecutableSourcePath = workspace.CurlExecutablePath, SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, Connect = new WinPeRuntimePayloadApplicationOptions { @@ -53,6 +54,7 @@ public async Task ProvisionAsync_StagesRequiredFilesAndExcludesWinPeOnlyArtifact Assert.Equal("{\"schemaVersion\":1}", await File.ReadAllTextAsync(Path.Combine(configPath, "foundry.connect.config.json"))); Assert.Equal("{\"schemaVersion\":2}", await File.ReadAllTextAsync(Path.Combine(configPath, "foundry.deploy.config.json"))); Assert.Equal("{\"zones\":[]}", await File.ReadAllTextAsync(Path.Combine(configPath, "iana-windows-timezones.json"))); + Assert.Equal("curl", await File.ReadAllTextAsync(Path.Combine(system32Path, "curl.exe"))); Assert.Equal("connect", await File.ReadAllTextAsync(Path.Combine(connectRuntimePath, "Foundry.Connect.exe"))); Assert.Equal("7za", await File.ReadAllTextAsync(Path.Combine(sevenZipToolsPath, "x64", "7za.exe"))); Assert.Equal("license", await File.ReadAllTextAsync(Path.Combine(sevenZipToolsPath, "License.txt"))); @@ -86,6 +88,7 @@ public async Task ProvisionAsync_WritesCmdOnlyRecoveryLauncher() FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", + CurlExecutableSourcePath = workspace.CurlExecutablePath, SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, Connect = new WinPeRuntimePayloadApplicationOptions { @@ -129,6 +132,7 @@ public async Task ProvisionAsync_WritesWinReConfigXmlAsUtf8WithoutBom() FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", + CurlExecutableSourcePath = workspace.CurlExecutablePath, SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, Connect = new WinPeRuntimePayloadApplicationOptions { @@ -171,6 +175,7 @@ public async Task ProvisionAsync_ReturnsBootMenuConfigurationForEverySupportedCu FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", + CurlExecutableSourcePath = workspace.CurlExecutablePath, SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, Connect = new WinPeRuntimePayloadApplicationOptions { @@ -214,6 +219,7 @@ public async Task ProvisionAsync_WhenBootMenuLocalizationIsMissingSupportedCultu FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", + CurlExecutableSourcePath = workspace.CurlExecutablePath, SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, Connect = new WinPeRuntimePayloadApplicationOptions { @@ -253,6 +259,7 @@ public async Task ProvisionAsync_WhenBootMenuTextExceedsThirtyCharacters_Returns FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", + CurlExecutableSourcePath = workspace.CurlExecutablePath, SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, Connect = new WinPeRuntimePayloadApplicationOptions { @@ -285,6 +292,7 @@ public async Task ProvisionAsync_WhenManagedPayloadExceedsBudget_ReturnsFailure( FoundryConnectConfigurationJson = "{}", DeployConfigurationJson = "{}", IanaWindowsTimeZoneMapJson = "{}", + CurlExecutableSourcePath = workspace.CurlExecutablePath, SevenZipSourceDirectoryPath = workspace.SevenZipSourcePath, MaxManagedPayloadSizeBytes = 32, Connect = new WinPeRuntimePayloadApplicationOptions @@ -353,10 +361,12 @@ private TempOsRecoveryWorkspace(string rootPath) MountedImagePath = Path.Combine(rootPath, "mount"); WorkingDirectoryPath = Path.Combine(rootPath, "work"); SevenZipSourcePath = Path.Combine(rootPath, "7z"); + CurlExecutablePath = Path.Combine(rootPath, "curl.exe"); Directory.CreateDirectory(MountedImagePath); Directory.CreateDirectory(WorkingDirectoryPath); Directory.CreateDirectory(Path.Combine(SevenZipSourcePath, "x64")); + File.WriteAllText(CurlExecutablePath, "curl"); File.WriteAllText(Path.Combine(SevenZipSourcePath, "x64", "7za.exe"), "7za"); File.WriteAllText(Path.Combine(SevenZipSourcePath, "License.txt"), "license"); File.WriteAllText(Path.Combine(SevenZipSourcePath, "readme.txt"), "readme"); @@ -366,6 +376,7 @@ private TempOsRecoveryWorkspace(string rootPath) public string MountedImagePath { get; } public string WorkingDirectoryPath { get; } public string SevenZipSourcePath { get; } + public string CurlExecutablePath { get; } public static TempOsRecoveryWorkspace Create() { diff --git a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs index 63d8d30d..318a38b0 100644 --- a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs +++ b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningOptions.cs @@ -10,6 +10,7 @@ public sealed record OsRecoveryPayloadProvisioningOptions public string FoundryConnectConfigurationJson { get; init; } = string.Empty; public string DeployConfigurationJson { get; init; } = string.Empty; public string IanaWindowsTimeZoneMapJson { get; init; } = string.Empty; + public string CurlExecutableSourcePath { get; init; } = string.Empty; public string SevenZipSourceDirectoryPath { get; init; } = string.Empty; public WinPeRuntimePayloadApplicationOptions Connect { get; init; } = new(); public IReadOnlyList BootMenuLocalizations { get; init; } = []; diff --git a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs index 6ecb0fda..99dd7319 100644 --- a/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs +++ b/src/Foundry.Core/Services/WinPe/OsRecovery/OsRecoveryPayloadProvisioningService.cs @@ -47,9 +47,11 @@ public async Task> ProvisionAsy { string mountedImagePath = Path.GetFullPath(options.MountedImagePath); string recoveryToolsPath = Path.Combine(mountedImagePath, "Sources", "Recovery", "Tools"); + string system32Path = Path.Combine(mountedImagePath, "Windows", "System32"); string foundryConfigPath = Path.Combine(mountedImagePath, "Foundry", "Config"); Directory.CreateDirectory(recoveryToolsPath); + Directory.CreateDirectory(system32Path); Directory.CreateDirectory(foundryConfigPath); string launcherContent = LoadLauncherContent(); @@ -82,6 +84,8 @@ await File.WriteAllTextAsync( Utf8NoBom, cancellationToken).ConfigureAwait(false); + File.Copy(options.CurlExecutableSourcePath, Path.Combine(system32Path, "curl.exe"), overwrite: true); + ProvisionBundledSevenZip(mountedImagePath, options); WinPeResult runtimeProvisioningResult = await _runtimePayloadProvisioningService.ProvisionAsync( @@ -217,6 +221,7 @@ private static long CalculateManagedPayloadSizeBytes(string mountedImagePath, Wi [ Path.Combine(mountedImagePath, "Sources", "Recovery", "Tools", LauncherFileName), Path.Combine(mountedImagePath, "Sources", "Recovery", "Tools", WinReConfigFileName), + Path.Combine(mountedImagePath, "Windows", "System32", "curl.exe"), Path.Combine(mountedImagePath, "Foundry", "Config", "foundry.connect.config.json"), Path.Combine(mountedImagePath, "Foundry", "Config", "foundry.deploy.config.json"), Path.Combine(mountedImagePath, "Foundry", "Config", "iana-windows-timezones.json"), @@ -347,6 +352,14 @@ private static string LoadLauncherContent() "Set OsRecoveryPayloadProvisioningOptions.SevenZipSourceDirectoryPath."); } + if (string.IsNullOrWhiteSpace(options.CurlExecutableSourcePath) || !File.Exists(options.CurlExecutableSourcePath)) + { + return new WinPeDiagnostic( + WinPeErrorCodes.ValidationFailed, + "curl.exe source path is required for OS recovery payload provisioning.", + $"Expected file: '{options.CurlExecutableSourcePath}'."); + } + if (!options.Connect.IsEnabled) { return new WinPeDiagnostic( diff --git a/src/Foundry.Deploy.Tests/ProvisionOsRecoveryStepTests.cs b/src/Foundry.Deploy.Tests/ProvisionOsRecoveryStepTests.cs index 7d286af7..f0e87f90 100644 --- a/src/Foundry.Deploy.Tests/ProvisionOsRecoveryStepTests.cs +++ b/src/Foundry.Deploy.Tests/ProvisionOsRecoveryStepTests.cs @@ -171,6 +171,7 @@ await File.WriteAllTextAsync( Assert.False(recoveryDeployDocument.Network.ProfileRoaming.IsEnabled); Assert.False(recoveryConnectDocument.Wifi.IsEnabled); Assert.False(recoveryConnectDocument.Dot1x.IsEnabled); + Assert.Equal(Path.Combine(Environment.SystemDirectory, "curl.exe"), provisioning.CapturedOptions.CurlExecutableSourcePath); } private static ProvisionOsRecoveryStep CreateStep(RecordingOsRecoveryPayloadProvisioningService provisioning) diff --git a/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs b/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs index bf05d384..093b681b 100644 --- a/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs +++ b/src/Foundry.Deploy/Services/Deployment/Steps/ProvisionOsRecoveryStep.cs @@ -126,6 +126,7 @@ await _osRecoveryPayloadProvisioningService.ProvisionAsync( FoundryConnectConfigurationJson = connectConfigurationJson, DeployConfigurationJson = deployConfigurationJson, IanaWindowsTimeZoneMapJson = _embeddedAssetService.GetIanaWindowsTimeZoneMapJson(), + CurlExecutableSourcePath = ResolveCurlExecutablePath(), SevenZipSourceDirectoryPath = _embeddedAssetService.GetSevenZipSourceDirectoryPath(), Connect = new WinPeRuntimePayloadApplicationOptions { @@ -353,6 +354,11 @@ private static WinPeArchitecture ResolveArchitecture(string? architecture) : WinPeArchitecture.X64; } + private static string ResolveCurlExecutablePath() + { + return Path.Combine(Environment.SystemDirectory, "curl.exe"); + } + private static string ResolveWinReImagePath(string recoveryPartitionRoot) { return Path.Combine(recoveryPartitionRoot, "Recovery", "WindowsRE", WinReImageFileName); From a4279bea8cb79cb99542c3e4da07a6a7eb4ad163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20CHAVE?= Date: Tue, 16 Jun 2026 00:14:03 +0200 Subject: [PATCH 11/11] fix(localization): shorten os recovery boot labels --- src/Foundry.Deploy/Strings/bg-BG/Resources.resx | 2 +- src/Foundry.Deploy/Strings/hr-HR/Resources.resx | 2 +- src/Foundry.Deploy/Strings/hu-HU/Resources.resx | 2 +- src/Foundry.Deploy/Strings/it-IT/Resources.resx | 2 +- src/Foundry.Deploy/Strings/lv-LV/Resources.resx | 2 +- src/Foundry.Deploy/Strings/pt-BR/Resources.resx | 2 +- src/Foundry.Deploy/Strings/pt-PT/Resources.resx | 2 +- src/Foundry.Deploy/Strings/ru-RU/Resources.resx | 2 +- src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Foundry.Deploy/Strings/bg-BG/Resources.resx b/src/Foundry.Deploy/Strings/bg-BG/Resources.resx index 92975a36..39af4b4b 100644 --- a/src/Foundry.Deploy/Strings/bg-BG/Resources.resx +++ b/src/Foundry.Deploy/Strings/bg-BG/Resources.resx @@ -529,5 +529,5 @@ Осигурено възстановяване на ОС. Осигурено възстановяване на ОС (симулация). Възстановяване Foundry OSD - Аварийно преинсталиране Windows + Аварийно внедряване Windows diff --git a/src/Foundry.Deploy/Strings/hr-HR/Resources.resx b/src/Foundry.Deploy/Strings/hr-HR/Resources.resx index 827172ec..bd437ffd 100644 --- a/src/Foundry.Deploy/Strings/hr-HR/Resources.resx +++ b/src/Foundry.Deploy/Strings/hr-HR/Resources.resx @@ -529,5 +529,5 @@ Nastaviti s oporavkom OS-a? Omogućen oporavak OS-a. Osiguran oporavak OS-a (simulacija). Foundry OSD oporavak - Hitno ponovno uvođenje Windowsa + Hitno uvođenje Windowsa diff --git a/src/Foundry.Deploy/Strings/hu-HU/Resources.resx b/src/Foundry.Deploy/Strings/hu-HU/Resources.resx index ed12efef..ff3f345e 100644 --- a/src/Foundry.Deploy/Strings/hu-HU/Resources.resx +++ b/src/Foundry.Deploy/Strings/hu-HU/Resources.resx @@ -529,5 +529,5 @@ Folytatja az OS helyreállítást? Az operációs rendszer helyreállítása biztosított. Az operációs rendszer helyreállítása biztosított (szimuláció). Foundry OSD-helyreállítás - Windows vészhelyzeti újratelepítése + Windows vészhelyzeti telepítés diff --git a/src/Foundry.Deploy/Strings/it-IT/Resources.resx b/src/Foundry.Deploy/Strings/it-IT/Resources.resx index 662aa4f3..262bded0 100644 --- a/src/Foundry.Deploy/Strings/it-IT/Resources.resx +++ b/src/Foundry.Deploy/Strings/it-IT/Resources.resx @@ -529,5 +529,5 @@ Continuare con il ripristino del sistema operativo? Effettuato il provisioning del ripristino del sistema operativo. Provisioning del ripristino del sistema operativo (simulazione). Ripristino Foundry OSD - Reinstallazione urgente Windows + Ripristino Windows urgente diff --git a/src/Foundry.Deploy/Strings/lv-LV/Resources.resx b/src/Foundry.Deploy/Strings/lv-LV/Resources.resx index 857223af..9ebfd43e 100644 --- a/src/Foundry.Deploy/Strings/lv-LV/Resources.resx +++ b/src/Foundry.Deploy/Strings/lv-LV/Resources.resx @@ -529,5 +529,5 @@ Vai turpināt ar OS atkopšanu? Nodrošināta OS atkopšana. Nodrošināta OS atkopšana (simulācija). Foundry OSD atkopšana - Windows ārkārtas pārizvietošana + Windows ārkārtas izvietošana diff --git a/src/Foundry.Deploy/Strings/pt-BR/Resources.resx b/src/Foundry.Deploy/Strings/pt-BR/Resources.resx index ebfce5ad..d58bf4f3 100644 --- a/src/Foundry.Deploy/Strings/pt-BR/Resources.resx +++ b/src/Foundry.Deploy/Strings/pt-BR/Resources.resx @@ -529,5 +529,5 @@ Continuar com a recuperação do sistema operacional? Recuperação do sistema operacional provisionada. Recuperação de SO provisionada (simulação). Recuperação Foundry OSD - Reimplantação Windows emergencial + Reimplantar Windows urgente diff --git a/src/Foundry.Deploy/Strings/pt-PT/Resources.resx b/src/Foundry.Deploy/Strings/pt-PT/Resources.resx index c942297b..404987b5 100644 --- a/src/Foundry.Deploy/Strings/pt-PT/Resources.resx +++ b/src/Foundry.Deploy/Strings/pt-PT/Resources.resx @@ -529,5 +529,5 @@ Continuar com a recuperação do sistema operacional? Recuperação do sistema operacional provisionada. Recuperação de SO provisionada (simulação). Recuperação Foundry OSD - Reimplementação Windows urgente + Reinstalar Windows urgente diff --git a/src/Foundry.Deploy/Strings/ru-RU/Resources.resx b/src/Foundry.Deploy/Strings/ru-RU/Resources.resx index eb849d22..94879209 100644 --- a/src/Foundry.Deploy/Strings/ru-RU/Resources.resx +++ b/src/Foundry.Deploy/Strings/ru-RU/Resources.resx @@ -529,5 +529,5 @@ Предусмотрено восстановление ОС. Предусмотрено восстановление ОС (симуляция). Восстановление Foundry OSD - Аварийное развертывание Windows + Аварийная установка Windows diff --git a/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx b/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx index 457866dc..2b24d27d 100644 --- a/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx +++ b/src/Foundry.Deploy/Strings/sr-Latn-RS/Resources.resx @@ -529,5 +529,5 @@ Nastaviti sa oporavkom OS-a? Oporavak OS-a je pripremljen. Oporavak OS-a je pripremljen (simulacija). Foundry OSD oporavak - Hitno ponovno uvođenje Windowsa + Hitno uvođenje Windowsa