<# .SYNOPSIS OSDCloud Logic secret gist .DESCRIPTION This script contains the logic for OSDCloud deployments with profile selection. .NOTES Version: 0.1 Creation Date: 01.12.2025 Author: Akos Bakos Company: SmartCon GmbH Contact: akos.bakos@smartcon.ch Copyright (c) 2025 SmartCon GmbH HISTORY: Date By Comments ---------- --- ---------------------------------------------------------- 01.12.2025 Akos Bakos Script created 26.03.2026 Akos Bakos Build change: 24H2 --> 25H2 Keyboard.ps1: Added logic to set keyboard layout to de-CH in specialize phase via unattend.xml Added logic to write Standort to registry for OOBE script #> if (-NOT (Test-Path 'X:\OSDCloud\Logs')) { New-Item -Path 'X:\OSDCloud\Logs' -ItemType Directory -Force -ErrorAction Stop | Out-Null } #Transport Layer Security (TLS) 1.2 [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 #[System.Net.WebRequest]::DefaultWebProxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials $Transcript = "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Start-OSDCloudLogic.log" Start-Transcript -Path (Join-Path "X:\OSDCloud\Logs" $Transcript) -ErrorAction Ignore | Out-Null #region User Selections # Step 1: Select Environment (Production or Testing) function Get-EnvironmentChoice { $validChoices = @('1', '2') $choiceDescriptions = @{ '1' = 'Production' '2' = 'Testing' } $isValidChoice = $false $userChoice = $null Write-Host "`nSelect Environment:" -ForegroundColor Cyan Write-Host "1 - Production" Write-Host "2 - Testing" while (-not $isValidChoice) { $userChoice = Read-Host "`nEnter your choice (1-2)" if ($validChoices -contains $userChoice) { $isValidChoice = $true Write-Host -NoNewline "You selected '" Write-Host -NoNewline -ForegroundColor Yellow "$($choiceDescriptions[$userChoice])" Write-Host -NoNewline "'" $confirmChoice = Read-Host ". Is this correct? (Y/N)" if ($confirmChoice -notmatch '^[Yy]') { $isValidChoice = $false Write-Host "Let's try again." -ForegroundColor Green } } else { Write-Host "Invalid choice. Please enter 1 or 2." -ForegroundColor Red } } return $userChoice } # Step 2: Select Development Mode function Get-DevelopmentMode { $isValidChoice = $false $userChoice = $null Write-Host "`nDevelopment Mode:" -ForegroundColor Cyan Write-Host "Enable development mode? (Y/N)" while (-not $isValidChoice) { $userChoice = Read-Host "`nEnter your choice (Y/N)" if ($userChoice -match '^[YyNn]$') { $isValidChoice = $true $devModeText = if ($userChoice -match '^[Yy]$') { "Yes (Development Mode Enabled)" } else { "No (Development Mode Disabled)" } Write-Host -NoNewline "You selected '" Write-Host -NoNewline -ForegroundColor Yellow "$devModeText" Write-Host -NoNewline "'" $confirmChoice = Read-Host ". Is this correct? (Y/N)" if ($confirmChoice -notmatch '^[Yy]') { $isValidChoice = $false Write-Host "Let's try again." -ForegroundColor Green } } else { Write-Host "Invalid choice. Please enter Y or N." -ForegroundColor Red } } return ($userChoice -match '^[Yy]$') } # Step 3: Select Standort (Location/Site) function Get-StandortSelection { $isValidChoice = $false $userInput = $null Write-Host "`nStandort Selection:" -ForegroundColor Cyan Write-Host "Please enter the Standort (location/site):" while (-not $isValidChoice) { $userInput = Read-Host "`nStandort" if (-not [string]::IsNullOrWhiteSpace($userInput)) { Write-Host -NoNewline "You entered '" Write-Host -NoNewline -ForegroundColor Yellow "$userInput" Write-Host -NoNewline "'" $confirmChoice = Read-Host ". Is this correct? (Y/N)" if ($confirmChoice -match '^[Yy]$') { $isValidChoice = $true } else { Write-Host "Let's try again." -ForegroundColor Green } } else { Write-Host "Standort cannot be empty. Please try again." -ForegroundColor Red } } return $userInput } # Get environment selection $environmentChoice = Get-EnvironmentChoice # Get development mode selection $developmentMode = Get-DevelopmentMode # Get Standort selection $Standort = Get-StandortSelection # Display final configuration Write-Host "`n========================================" -ForegroundColor Cyan Write-Host "Configuration Summary:" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan if ($environmentChoice -eq '1') { Write-Host "Environment: " -NoNewline Write-Host "Production" -ForegroundColor Green } else { Write-Host "Environment: " -NoNewline Write-Host "Testing" -ForegroundColor Yellow } Write-Host "Development Mode: " -NoNewline if ($developmentMode) { Write-Host "Enabled" -ForegroundColor Yellow } else { Write-Host "Disabled" -ForegroundColor Green } Write-Host "========================================`n" -ForegroundColor Cyan # Proceed with script logic based on selections Write-Host "Proceeding with OSD configuration...`n" -ForegroundColor Green # Example: Set variables based on selections $isProduction = ($environmentChoice -eq '1') $isDevelopment = $developmentMode #endregion #================================================ Write-Host -ForegroundColor DarkGray "=========================================================================" Write-Host -ForegroundColor DarkGray "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) " -NoNewline Write-Host -ForegroundColor Cyan "[PreOS] Update Module" #================================================ # Write-Host -ForegroundColor Green "Updating OSD PowerShell Module" # Install-Module OSD -Force Write-Host -ForegroundColor Green "Overwrite Save-WebFile.ps1 in OSD Module (Newest Version)" $SourceFile = "X:\MBClient\Save-WebFile.ps1" $OSDModulePath = "X:\Program Files\WindowsPowerShell\Modules\OSD" # Find the newest OSD module version folder if (Test-Path $OSDModulePath) { # Get all version subfolders and sort by version number (newest first) $VersionFolders = Get-ChildItem -Path $OSDModulePath -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^\d+\.\d+' } | Sort-Object { [version]$_.Name } -Descending if ($VersionFolders) { $NewestVersion = $VersionFolders | Select-Object -First 1 Write-Host "Found newest OSD module version: $($NewestVersion.Name)" -ForegroundColor Cyan # Build path to Save-WebFile.ps1 in Public\Functions\Other subfolder $DestinationFile = Join-Path -Path $NewestVersion.FullName -ChildPath "Public\Functions\Other\Save-WebFile.ps1" if (Test-Path $DestinationFile) { Write-Host "Found Save-WebFile.ps1 at: $DestinationFile" -ForegroundColor Green # Check if source file exists if (Test-Path $SourceFile) { Write-Host "Copying $SourceFile to $DestinationFile" -ForegroundColor Yellow Copy-Item -Path $SourceFile -Destination $DestinationFile -Force Write-Host "File overwritten successfully in version $($NewestVersion.Name)!" -ForegroundColor Green } else { Write-Host "Source file not found: $SourceFile" -ForegroundColor Red } } else { Write-Host "Save-WebFile.ps1 not found at: $DestinationFile" -ForegroundColor Red Write-Host "Expected location: Public\Functions\Other\Save-WebFile.ps1" -ForegroundColor Yellow } } else { Write-Host "No version folders found in OSD module directory" -ForegroundColor Red } } else { Write-Host "OSD module path not found: $OSDModulePath" -ForegroundColor Red } Write-Host -ForegroundColor DarkGray "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) " -NoNewline Write-Host -ForegroundColor Green "Importing OSD PowerShell Module" Import-Module OSD -Force #region Helper Functions function Write-DarkGrayDate { [CmdletBinding()] param ( [Parameter(Position = 0)] [System.String] $Message ) if ($Message) { Write-Host -ForegroundColor DarkGray "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) $Message" } else { Write-Host -ForegroundColor DarkGray "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) " -NoNewline } } function Write-DarkGrayHost { [CmdletBinding()] param ( [Parameter(Mandatory = $true, Position = 0)] [System.String] $Message ) Write-Host -ForegroundColor DarkGray $Message } function Write-DarkGrayLine { [CmdletBinding()] param () Write-Host -ForegroundColor DarkGray "=========================================================================" } function Write-SectionHeader { [CmdletBinding()] param ( [Parameter(Mandatory = $true, Position = 0)] [System.String] $Message ) Write-DarkGrayLine Write-DarkGrayDate Write-Host -ForegroundColor Cyan $Message } function Write-SectionSuccess { [CmdletBinding()] param ( [Parameter(Position = 0)] [System.String] $Message = 'Success!' ) Write-DarkGrayDate Write-Host -ForegroundColor Green $Message } #endregion #region PreOS Tasks #======================================================================= Write-SectionHeader "[PreOS] Define OSDCloud Global Parameters" #======================================================================= $Global:MyOSDCloud = [ordered]@{ MSCatalogFirmware = [bool]$true HPBIOSUpdate = [bool]$true } Write-SectionHeader "MyOSDCloud variables" Write-Host ($Global:MyOSDCloud | Out-String) if ($Global:OSDCloud.ApplyCatalogFirmware -eq $true) { #======================================================================= Write-SectionHeader "[PreOS] Prepare Firmware Tasks" #======================================================================= #Register-PSRepository -Default -Verbose osdcloud-TrustPSGallery -Verbose #Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -Verbose osdcloud-InstallPowerShellModule -Name 'MSCatalog' #Install-Module -Name MSCatalog -Force -Verbose -SkipPublisherCheck -AllowClobber -Repository PSGallery } #endregion #region OS Tasks #======================================================================= Write-SectionHeader "[OS] Params and Start-OSDCloud" #======================================================================= $Params = @{ OSVersion = "Windows 11" OSBuild = "25H2" OSEdition = "Pro" OSLanguage = "en-us" OSLicense = "Retail" ZTI = $true Firmware = $true } Write-Host ($Params | Out-String) Start-OSDCloud @Params #endregion #region Autopilot Tasks #================================================ Write-SectionHeader "[PostOS] Define Autopilot Attributes" #================================================ $Serial = Get-WmiObject Win32_bios | Select-Object -ExpandProperty SerialNumber $lastFiveChars = $serial.Substring($serial.Length - 5) Write-DarkGrayHost "Define Computername" $prefix = 'MBE' $Serial = Get-WmiObject Win32_BIOS | Select-Object -ExpandProperty SerialNumber $lastFiveChars = $serial.Substring($serial.Length - 6) $AssignedComputerName = "$prefix$lastFiveChars" Write-Host "Using computer name: $AssignedComputerName" -ForegroundColor Yellow Write-DarkGrayHost "Define Entra ID Group" $AddToGroup = if ($isProduction) { 'AG-NLDZ-PRD-All-Clients' } else { 'AG-NLDZ-INT-All-Clients' } Write-Host "Using Entra ID Group: $AddToGroup" -ForegroundColor Yellow $GroupTag = $prefix Write-Host "Using GroupTag: $GroupTag" -ForegroundColor Yellow #================================================ Write-SectionHeader "[PostOS] AutopilotOOBE Configuration" #================================================ Write-DarkGrayHost "Create C:\ProgramData\OSDeploy\OSDeploy.AutopilotOOBE.json file" $AutopilotOOBEJson = @" { "AssignedComputerName" : "$AssignedComputerName", "AddToGroup": "$AddToGroup", "Assign": { "IsPresent": true }, "GroupTag": "$GroupTag", "Hidden": [ "AddToGroup", "AssignedUser", "PostAction", "GroupTag", "Assign" ], "PostAction": "Quit", "Run": "NetworkingWireless", "Docs": "https://google.com/", "Title": "Autopilot Register" } "@ If (!(Test-Path "C:\ProgramData\OSDeploy")) { New-Item "C:\ProgramData\OSDeploy" -ItemType Directory -Force | Out-Null } $AutopilotOOBEJson | Out-File -FilePath "C:\ProgramData\OSDeploy\OSDeploy.AutopilotOOBE.json" -Encoding ascii -Force #endregion #region Specialize Tasks #================================================ Write-SectionHeader "[PostOS] SetupComplete CMD Command Line" #================================================ Write-DarkGrayHost "Cleanup SetupComplete Files from OSDCloud Module" Get-ChildItem -Path 'C:\Windows\Setup\Scripts\SetupComplete*' -Recurse | Remove-Item -Force #================================================= Write-SectionHeader "[PostOS] Define Specialize Phase" #================================================= $UnattendXml = @' 1 Set Keyboard Layout PowerShell -ExecutionPolicy Bypass C:\Windows\Setup\scripts\Keyboard.ps1 2 Start Autopilot Import & Assignment Process PowerShell -ExecutionPolicy Bypass C:\Windows\Setup\scripts\Autopilot.ps1 de-CH de-DE de-DE de-CH '@ # Get-OSDGather -Property IsWinPE Block-WinOS if (-NOT (Test-Path 'C:\Windows\Panther')) { New-Item -Path 'C:\Windows\Panther'-ItemType Directory -Force -ErrorAction Stop | Out-Null } $Panther = 'C:\Windows\Panther' $UnattendPath = "$Panther\Unattend.xml" $UnattendXml | Out-File -FilePath $UnattendPath -Encoding utf8 -Width 2000 -Force Write-DarkGrayHost "Use-WindowsUnattend -Path 'C:\' -UnattendPath $UnattendPath" Use-WindowsUnattend -Path 'C:\' -UnattendPath $UnattendPath | Out-Null #endregion #region OOBE Tasks #================================================ Write-SectionHeader "[PostOS] OOBE CMD Command Line" #================================================ Write-DarkGrayHost "Copy scripts for OOBE and specialize phase" Copy-Item X:\MBClient\Autopilot.ps1 C:\Windows\Setup\scripts -Force Copy-Item X:\MBClient\OOBE.ps1 C:\Windows\Setup\scripts -Force Copy-Item X:\MBClient\Cleanup.ps1 C:\Windows\Setup\scripts -Force Copy-Item X:\MBClient\Keyboard.ps1 C:\Windows\Setup\scripts -Force $OOBEcmdTasks = @' @echo off REM Execute OOBE Tasks start /wait powershell.exe -NoL -ExecutionPolicy Bypass -F C:\Windows\Setup\Scripts\oobe.ps1 REM Execute Cleanup Script start /wait powershell.exe -NoL -ExecutionPolicy Bypass -F C:\Windows\Setup\Scripts\cleanup.ps1 REM Below a PS session for debug and testing in system context, # when not needed REM start /wait powershell.exe -NoL -ExecutionPolicy Bypass exit '@ Write-Host "Create C:\Windows\Setup\scripts\oobe.cmd for automatically execution in the OOBE phase" -ForegroundColor Yellow $OOBEcmdTasks | Out-File -FilePath 'C:\Windows\Setup\scripts\oobe.cmd' -Encoding ascii -Force Write-DarkGrayHost "Copying PFX file and import-cert.ps1 script" Copy-Item X:\MBClient\Import-Cert.ps1 C:\OSDCloud\ -Force Copy-Item X:\MBClient\OSDCloudRegistration.pfx C:\OSDCloud\ -Force #endregion Write-DarkGrayHost "Disabling Shift+F10 in OOBE for security Reasons" $Tagpath = "C:\Windows\Setup\Scripts\DisableCMDRequest.TAG" New-Item -ItemType file -Force -Path $Tagpath | Out-Null Write-DarkGrayHost "Shift F10 disabled now!" #region Development if ($developmentMode -eq $true) { Write-SectionHeader "Enabling Shift+F10 back in OOBE for DEVELOPMENT mode and for PCs" $Tagpath = "C:\Windows\Setup\Scripts\DisableCMDRequest.TAG" Remove-Item -Force -Path $Tagpath | Out-Null Write-Host "Shift F10 enabled now!" -ForegroundColor Yellow Write-Host "Disable Cursor Suppression" -ForegroundColor Yellow #cmd.exe /c reg load HKLM\Offline c:\windows\system32\config\software & cmd.exe /c REG ADD "HKLM\Offline\Microsoft\Windows\CurrentVersion\Policies\System" /v EnableCursorSuppression /t REG_DWORD /d 0 /f & cmd.exe /c reg unload HKLM\Offline Invoke-Exe cmd.exe -Arguments "/c reg load HKLM\Offline c:\windows\system32\config\software" New-ItemProperty -Path HKLM:\Offline\Microsoft\Windows\CurrentVersion\Policies\System -Name EnableCursorSuppression -Value 0 -Force #Invoke-Exe cmd.exe -Arguments "/c REG ADD 'HKLM\Offline\Microsoft\Windows\CurrentVersion\Policies\System' /v EnableCursorSuppression /t REG_DWORD /d 0 /f " Invoke-Exe cmd.exe -Arguments "/c reg unload HKLM\Offline" } #endregion #region Standort in registry for OOBE Script Write-SectionHeader "Write Standort to Registry for OOBE Script" Invoke-Exe cmd.exe -Arguments "/c reg load HKLM\OfflineSoftware C:\Windows\System32\config\SOFTWARE" if (-not (Test-Path "HKLM:\OfflineSoftware\MigrosBank\KioVariables")) { New-Item -Path "HKLM:\OfflineSoftware\MigrosBank\KioVariables" -Force } New-ItemProperty -Path "HKLM:\OfflineSoftware\MigrosBank\KioVariables" -Name Standort -Value $Standort -PropertyType String -Force # Verify $regValue = Get-ItemProperty -Path "HKLM:\OfflineSoftware\MigrosBank\KioVariables" -Name Standort -ErrorAction SilentlyContinue if ($regValue.Standort -eq $Standort) { Write-DarkGrayHost "Registry key 'Standort' successfully set to '$Standort'." } else { Write-Warning "Failed to set registry key 'Standort' in offline hive." } $regValue = $null [GC]::Collect() [GC]::WaitForPendingFinalizers() Start-Sleep -Seconds 2 Invoke-Exe cmd.exe -Arguments "/c reg unload HKLM\OfflineSoftware" #endregion #======================================================================= Write-SectionHeader "Copy OSDCloud logs to IntuneManagementExtension\Logs\OSD" #======================================================================= if (-NOT (Test-Path 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\OSD')) { New-Item -Path 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\OSD' -ItemType Directory -Force -ErrorAction Stop | Out-Null } Get-ChildItem -Path X:\OSDCloud\Logs\ | Copy-Item -Destination 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\OSD' -Force if ($developmentMode -eq $false) { Write-DarkGrayHost "Restarting in 20 seconds!" Start-Sleep -Seconds 20 wpeutil reboot Stop-Transcript | Out-Null } else { Write-DarkGrayHost "Development Mode - No reboot!" Stop-Transcript | Out-Null }