Created
March 20, 2026 08:20
-
-
Save MarcelMeurer/fc4b8d14ae0a469c83cdc0511fc07607 to your computer and use it in GitHub Desktop.
NVMe handler for v6+ hosts in Hydra (only to use with a script collection)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| param( | |
| [string] $Mode, | |
| [bool] $DoNotRestart = $false, | |
| [string] $LogDir = "$env:windir\system32\logfiles" | |
| ) | |
| function LogWriter($message) { | |
| $message = "$(Get-Date ([datetime]::UtcNow) -Format "o") $message" | |
| write-host($message) | |
| if ([System.IO.Directory]::Exists($LogDir)) { try { write-output($message) | Out-File $LogFile -Append } catch {} } | |
| } | |
| function Read-IniFile($path) { | |
| $ini = @{} | |
| $section = "" | |
| foreach ($line in [System.IO.File]::ReadAllLines($path, [System.Text.Encoding]::Unicode)) { | |
| if ($line -match "^\[(.+)\]") { | |
| $section = $matches[1] | |
| $ini[$section] = @{} | |
| } | |
| elseif ($line -match "^(.+?)=(.*)$" -and $section) { | |
| $ini[$section][$matches[1]] = $matches[2] | |
| } | |
| } | |
| return $ini | |
| } | |
| function Write-IniFile($path, $ini) { | |
| $lines = @() | |
| foreach ($section in $ini.Keys) { | |
| $lines += "[$section]" | |
| foreach ($key in $ini[$section].Keys) { | |
| $lines += "$key=$($ini[$section][$key])" | |
| } | |
| $lines += "" | |
| } | |
| $iniFile = Get-Item -Path $path -Force -ErrorAction SilentlyContinue | |
| if ($iniFile) { | |
| $iniFile.Attributes = $iniFile.Attributes -band (-bnot [System.IO.FileAttributes]::Hidden) | |
| } | |
| [System.IO.File]::WriteAllText($path, ($lines -join "`r`n"), [System.Text.Encoding]::Unicode) | |
| if ($iniFile) { | |
| $iniFile.Attributes = $iniFile.Attributes -bor [System.IO.FileAttributes]::Hidden | |
| } | |
| } | |
| function Add-ShutdownScript($iniPath, $scriptFile, $parameters) { | |
| $ini = if (Test-Path $iniPath) { Read-IniFile $iniPath } else { @{} } | |
| if (-not $ini.ContainsKey("Shutdown")) { $ini["Shutdown"] = @{} } | |
| $exists = $ini["Shutdown"].Keys | Where-Object { | |
| $_ -match "^\d+CmdLine$" -and $ini["Shutdown"][$_] -eq $scriptFile | |
| } | |
| if ($exists) { return $false } | |
| $index = ($ini["Shutdown"].Keys | Where-Object { $_ -match "^\d+CmdLine$" } | | |
| ForEach-Object { [int]($_ -replace "CmdLine", "") } | | |
| Measure-Object -Maximum).Maximum | |
| $index = if ($ini["Shutdown"].Count -eq 0) { 0 } else { $index + 1 } | |
| $ini["Shutdown"]["${index}CmdLine"] = $scriptFile | |
| $ini["Shutdown"]["${index}Parameters"] = $parameters | |
| Write-IniFile $iniPath $ini | |
| return $true | |
| } | |
| function Remove-ShutdownScript($iniPath, $scriptFile) { | |
| if (-not (Test-Path $iniPath)) { return $false } | |
| $ini = Read-IniFile $iniPath | |
| if (-not $ini.ContainsKey("Shutdown")) { return $false } | |
| $foundIndex = $ini["Shutdown"].Keys | Where-Object { | |
| $_ -match "^\d+CmdLine$" -and $ini["Shutdown"][$_] -eq $scriptFile | |
| } | ForEach-Object { $_ -replace "CmdLine", "" } | Select-Object -First 1 | |
| if ($null -eq $foundIndex) { return $false } | |
| $ini["Shutdown"].Remove("${foundIndex}CmdLine") | |
| $ini["Shutdown"].Remove("${foundIndex}Parameters") | |
| Write-IniFile $iniPath $ini | |
| return $true | |
| } | |
| function RedirectPageFileTo($drive) { | |
| LogWriter("Redirecting pagefile to drive $($drive):") | |
| $CurrentPageFile = Get-WmiObject -Query 'select * from Win32_PageFileSetting' | |
| if ($null -ne $CurrentPageFile -and $CurrentPageFile.Name -ne "") { | |
| LogWriter("Existing pagefile name: '$($CurrentPageFile.Name)', max size: $($CurrentPageFile.MaximumSize)") | |
| if ($CurrentPageFile) { | |
| try { | |
| $CurrentPageFile.delete() | |
| LogWriter("Pagefile deleted") | |
| } | |
| catch { | |
| LogWriter("Pagefile deletion error: $_") | |
| } | |
| } | |
| $CurrentPageFile = Get-WmiObject -Query 'select * from Win32_PageFileSetting' | |
| if ($null -eq $CurrentPageFile) { | |
| LogWriter("Pagefile deletion successful") | |
| } | |
| else { | |
| LogWriter("Pagefile deletion failed") | |
| } | |
| } | |
| Set-WMIInstance -Class Win32_PageFileSetting -Arguments @{name = "$($drive):\pagefile.sys"; InitialSize = 0; MaximumSize = 0 } | |
| $CurrentPageFile = Get-WmiObject -Query 'select * from Win32_PageFileSetting' | |
| if ($null -eq $CurrentPageFile) { | |
| LogWriter("Pagefile not found") | |
| } | |
| else { | |
| LogWriter("New pagefile name: '$($CurrentPageFile.Name)', max size: $($CurrentPageFile.MaximumSize)") | |
| } | |
| } | |
| $LogFile = $LogDir + "\AVD.NvemPreparation.log" | |
| $targetScriptFile = ($env:windir + "\AVD.NVMEPageFileHandling.ps1") | |
| $iniPath = "$env:windir\System32\GroupPolicy\Machine\Scripts\psscripts.ini" | |
| $services = @( | |
| "WindowsAzureGuestAgent" | |
| "RDAgentBootLoader" | |
| ) | |
| $ErrorActionPreference = "Stop" | |
| $runAsWell=$false | |
| if ($mode -like "installandrun") { | |
| $runAsWell=$true | |
| $mode="install" | |
| } | |
| if ($mode -like "install") { | |
| LogWriter("Copy script to windir") | |
| Copy-Item "$($MyInvocation.InvocationName)" -Destination $targetScriptFile | |
| LogWriter("Creating schedule task to run the script at startup") | |
| $action = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-executionPolicy Unrestricted -File `"$targetScriptFile`" -Mode `"NVMEPageFileHandling`"" | |
| $trigger = New-ScheduledTaskTrigger -AtStartup | |
| $principal = New-ScheduledTaskPrincipal 'NT Authority\SYSTEM' -RunLevel Highest | |
| $settingsSet = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit 0 | |
| $task = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger -Settings $settingsSet | |
| Register-ScheduledTask -TaskName 'ITPC-AVD-NVMEPageFileHandling' -InputObject $task -ErrorAction Ignore | |
| $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() | |
| do { | |
| $task = Get-ScheduledTask -TaskName "ITPC-AVD-NVMEPageFileHandling" -ErrorAction SilentlyContinue | |
| if (!$task) { Start-Sleep -Seconds 1 } | |
| } until ($task -or $stopwatch.Elapsed.TotalSeconds -ge 30) | |
| if ($task) { | |
| } | |
| else { | |
| throw "ITPC-AVD-NVMEPageFileHandling could not be created" | |
| } | |
| LogWriter("Preparing services") | |
| foreach ($svc in $services) { | |
| if (Get-Service -Name $svc -ErrorAction SilentlyContinue) { | |
| LogWriter("Setting service $svc to disabled") | |
| Set-Service -Name $svc -StartupType Disabled | |
| } | |
| } | |
| LogWriter("Configuring shutdown/restart entry point") | |
| $folder = Split-Path -Path $iniPath -Parent | |
| if (-not (Test-Path $folder)) { | |
| New-Item -Path $folder -ItemType Directory -Force | Out-Null | |
| } | |
| if (-not (Test-Path $iniPath)) { | |
| New-Item -Path $iniPath -ItemType File | Out-Null | |
| } | |
| if (Add-ShutdownScript $iniPath $targetScriptFile "-mode Shutdown") { | |
| LogWriter("Shutdown entry added to scripts.ini") | |
| } | |
| else { | |
| LogWriter("Shutdown entry already exists in scripts.ini") | |
| } | |
| $gptPath = "$env:windir\System32\GroupPolicy\gpt.ini" | |
| if (-not (Test-Path $gptPath)) { | |
| $gptContent = "[General]`r`ngPCMachineExtensionNames=[{42B5FAAE-6536-11D2-AE5A-0000F87571E3}{827D319E-6EAC-11D2-A4EA-00C04F79F83A}]`r`nVersion=1`r`n" | |
| [System.IO.File]::WriteAllText($gptPath, $gptContent, [System.Text.Encoding]::Unicode) | |
| LogWriter("Creating gpt.ini") | |
| } | |
| $scriptIniPath = "$env:windir\System32\GroupPolicy\Machine\Scripts\scripts.ini" | |
| if (-not (Test-Path $scriptIniPath)) { | |
| [System.IO.File]::WriteAllText($scriptIniPath, "", [System.Text.Encoding]::Unicode) | |
| LogWriter("Creating scripts.ini") | |
| } | |
| LogWriter("Running gpupdate") | |
| & gpupdate.exe /target:computer /force | |
| if ($runAsWell) { | |
| LogWriter("Running script") | |
| & $targetScriptFile -DoNotRestart $DoNotRestart | |
| } | |
| } | |
| elseif ($mode -like "uninstall") { | |
| LogWriter("Deleting schedule task") | |
| Stop-ScheduledTask -TaskName "ITPC-AVD-NVMEPageFileHandling" -ErrorAction SilentlyContinue | |
| Unregister-ScheduledTask -TaskName "ITPC-AVD-NVMEPageFileHandling" -confirm:$false -ErrorAction SilentlyContinue | |
| LogWriter("Preparing services to start automatically") | |
| foreach ($svc in $services) { | |
| if (Get-Service -Name $svc -ErrorAction SilentlyContinue) { | |
| LogWriter("Setting service $svc to Automatic") | |
| Set-Service -Name $svc -StartupType Automatic | |
| Start-Service -Name $svc -ErrorAction SilentlyContinue | |
| } | |
| } | |
| LogWriter("Removing shutdown entry from scripts.ini") | |
| if (Remove-ShutdownScript $iniPath $targetScriptFile) { | |
| LogWriter("Shutdown entry removed from scripts.ini") | |
| } | |
| else { | |
| LogWriter("Shutdown entry not found in scripts.ini") | |
| } | |
| LogWriter("Running gpupdate") | |
| & gpupdate.exe /target:computer /force | |
| } | |
| elseif ($mode -like "ShutDown") { | |
| LogWriter("Starting NVME Pagefile Handling on shutdown/reboot") | |
| try { | |
| foreach ($svc in $services2) { | |
| $regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$svc" | |
| $startValue = (Get-ItemProperty -Path $regPath -Name Start).Start | |
| if ($startValue -ne 4) { | |
| LogWriter("Setting service $svc to disabled") | |
| Set-Service -Name $svc -StartupType Disabled | |
| } | |
| } | |
| } | |
| catch { | |
| LogWriter("Service $svc not found: $_") | |
| } | |
| LogWriter("NVME Pagefile Handling on shutdown/reboot completed") | |
| } | |
| else { | |
| $doNotStartServices = $false | |
| try { | |
| LogWriter("Starting NVME Pagefile Handling") | |
| $tempDrive = "" | |
| $disks = @(Get-Disk | Where-Object { $_.FriendlyName -like "*Microsoft NVMe Direct Disk v2*" }) | |
| if ($disks -and $disks.Count -gt 0) { | |
| LogWriter("Found NVMe disk") | |
| $disk = $disks[0] | |
| $partitions = $disk | Get-Partition | where-object { $_.DriveLetter } | |
| if (-not $partitions) { | |
| if (-not $tempDrive) { | |
| $used = @((Get-Volume).DriveLetter) | |
| $tempDrive = [char[]](67..90) | Where-Object { $_ -notin $used } | Select-Object -First 1 | |
| } | |
| LogWriter("The NVMe disk has no partitions. Create and mount a partition as $($tempDrive):\") | |
| $baseHklm = "hklm:\Software\ITProCloud\WVD.Runtime" | |
| $lastStart = [datetime]::MinValue | |
| if (Test-Path $baseHklm) { | |
| try { | |
| $value = (Get-ItemProperty -Path $baseHklm -Name "NVMe-Handling.LastStart" -ErrorAction Stop)."NVMe-Handling.LastStart" | |
| $lastStart = [datetime]::Parse($value, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::RoundtripKind) | |
| } | |
| catch {} | |
| } | |
| if (-not (Test-Path $baseHklm)) { New-Item -Path $baseHklm -Force | Out-Null } | |
| $newTimestamp = (Get-Date).ToUniversalTime().ToString("o") | |
| $seconds = [int64]([datetime]::Parse($newTimestamp, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::RoundtripKind) - $lastStart).TotalSeconds | |
| if ($seconds -gt 180) { | |
| $disk | Initialize-Disk -PartitionStyle GPT -PassThru -ErrorAction SilentlyContinue | |
| $disk | New-Partition -UseMaximumSize -DriveLetter $tempDrive | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Temporary Storage" -force -Confirm:$false | |
| LogWriter("Configuring pagefile to use $($tempDrive):\") | |
| RedirectPageFileTo $tempDrive | |
| if ($DoNotRestart -eq $false) { | |
| LogWriter("Restarting computer to move the pagefile") | |
| $doNotStartServices = $true | |
| Restart-Computer -Force | |
| } | |
| else { | |
| LogWriter("DoNotRestart is set to true. Skipping restart") | |
| } | |
| } | |
| else { | |
| throw "Last disk modification happens less the 180 seconds ago. We may run into an issue. Quittung disk modification to prevent endless loops" | |
| } | |
| Set-ItemProperty -Path $baseHklm -Name "NVMe-Handling.LastStart" -Value $newTimestamp -Type String | |
| } | |
| else { | |
| LogWriter("NVMe disk is has a partition. Nothing to do") | |
| } | |
| } | |
| else { | |
| LogWriter("No NVMe disk found") | |
| } | |
| } | |
| catch { | |
| LogWriter("Error: $_") | |
| } | |
| finally { | |
| if ($doNotStartServices -eq $false) { | |
| LogWriter("Starting services") | |
| foreach ($svc in $services) { | |
| if (Get-Service -Name $svc -ErrorAction SilentlyContinue) { | |
| LogWriter("Starting service $svc") | |
| try { | |
| Set-Service -Name $svc -StartupType Automatic | |
| Start-Service -Name $svc | |
| } | |
| catch { | |
| LogWriter("Cannot start service $($svc): $_") | |
| } | |
| } | |
| } | |
| } | |
| LogWriter("Preparing services") | |
| foreach ($svc in $services) { | |
| if (Get-Service -Name $svc -ErrorAction SilentlyContinue) { | |
| LogWriter("Setting service $svc to disabled") | |
| Set-Service -Name $svc -StartupType Disabled -ErrorAction SilentlyContinue | |
| } | |
| } | |
| LogWriter("NVME Pagefile Handling completed") | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment