Created
May 26, 2026 16:14
-
-
Save danner26/261d066dba63a868eda359325f27ec87 to your computer and use it in GitHub Desktop.
Remove-AutoFsTxFromWinRE.ps1 NinjaOne Custom Field
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
| <# | |
| .SYNOPSIS | |
| Removes autofstx.exe from the WinRE BootExecute registry value. | |
| .DESCRIPTION | |
| This remediation script removes the "autofstx.exe" entry from the BootExecute | |
| REG_MULTI_SZ value inside the Windows Recovery Environment (WinRE) offline | |
| SYSTEM registry hive. This is a security fix to prevent autofstx.exe from | |
| executing during WinRE boot. | |
| The script performs the following steps: | |
| 1. Verify administrator privileges | |
| 2. Verify WinRE is enabled | |
| 3. Mount the WinRE image via reagentc | |
| 4. Load the offline SYSTEM registry hive | |
| 5. Read BootExecute and remove autofstx.exe if present | |
| 6. Unload the offline hive | |
| 7. Unmount the WinRE image with commit | |
| 8. Disable and re-enable WinRE to re-seal BitLocker trust chain | |
| Exit 0 = Success (entry removed or not present) | |
| Exit 1 = Failure (error during remediation) | |
| .PARAMETER MountPath | |
| Directory to use as the WinRE mount point. Created if it does not exist. | |
| Default: C:\Mount | |
| .EXAMPLE | |
| # Standard run | |
| .\Remove-AutoFsTxFromWinRE.ps1 | |
| .EXAMPLE | |
| # Custom mount path | |
| .\Remove-AutoFsTxFromWinRE.ps1 -MountPath D:\mount | |
| .NOTES | |
| Requirements: Windows with WinRE and reagentc support, Administrator privileges. | |
| The WinRE disable/enable cycle at the end is required to re-seal the | |
| BitLocker measurement chain after modifying the WinRE image. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| SOFTWARE. | |
| #> | |
| param( | |
| [Parameter(Mandatory = $false)] | |
| [string]$MountPath = "C:\Mount", | |
| [Parameter()] | |
| [String]$TextCustomFieldName | |
| ) | |
| if ($env:textCustomFieldName) { $TextCustomFieldName = $env:textCustomFieldName } | |
| # Target entry to remove from BootExecute | |
| $EntryToRemove = "autofstx.exe" | |
| # Internal constant for the offline hive key name | |
| $HiveName = "WinREHive" | |
| # State tracking for cleanup | |
| $hiveLoaded = $false | |
| $imageMounted = $false | |
| $mountCreated = $false | |
| $changesMade = $false | |
| Write-Host "Remove autofstx.exe from WinRE BootExecute" | |
| # Validate the text custom field name if provided | |
| if ($TextCustomFieldName) { | |
| # Ensure the field name is not just whitespace | |
| if ([string]::IsNullOrWhiteSpace($TextCustomFieldName)) { | |
| Write-Host "[Error] The 'Text Custom Field Name' parameter contains only spaces. Please provide a valid field name or leave it blank." | |
| exit 1 | |
| } | |
| # Trim leading and trailing whitespace from the field name | |
| $TextCustomFieldName = $TextCustomFieldName.Trim() | |
| # Ensure the field name contains only alphanumeric characters or underscores | |
| if ($TextCustomFieldName -match "[^0-9A-Za-z_]") { | |
| Write-Host -Object "[Error] The 'Text Custom Field Name' of '$TextCustomFieldName' contains invalid characters." | |
| Write-Host -Object "[Error] Please provide a valid text custom field name to save the results, or leave it blank." | |
| Write-Host -Object "[Error] https://ninjarmm.zendesk.com/hc/articles/360060920631-Custom-Field-Setup" | |
| exit 1 | |
| } | |
| } | |
| # Write the remediation result to the NinjaOne custom field, if one was provided. | |
| # Policy: a failure to write the field always results in exit 1 - on success paths this | |
| # overrides exit 0; on failure paths it is equivalent to the trailing exit 1. | |
| function Set-NinjaResult { | |
| param([Parameter(Mandatory)][string]$Status) | |
| if (-not $TextCustomFieldName) { return } | |
| try { | |
| Write-Host "Attempting to set the custom field '$TextCustomFieldName'." | |
| Ninja-Property-Set $TextCustomFieldName $Status | |
| Write-Host "Success." | |
| } catch { | |
| Write-Host "[Error] $($_.Exception.Message)" | |
| Write-Host "[Error] Failed to set the custom field '$TextCustomFieldName'." | |
| exit 1 | |
| } | |
| } | |
| # 1. Verify Administrator Privileges | |
| # PS Version: All | Admin: Yes | System Requirements: None | |
| try { | |
| $identity = [Security.Principal.WindowsIdentity]::GetCurrent() | |
| $principal = [Security.Principal.WindowsPrincipal]$identity | |
| $isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| if (-not $isAdmin) { | |
| Write-Host "[1/8] Administrator check: FAILED" -ForegroundColor Red | |
| Write-Host " This script must be run as Administrator." | |
| Write-Host " Right-click PowerShell and select 'Run as administrator'." | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| Write-Host "[1/8] Administrator check: Passed" -ForegroundColor Green | |
| } catch { | |
| Write-Warning "Error checking administrator privileges: $_" | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| # 2. Check WinRE Status | |
| # PS Version: All | Admin: Yes | System Requirements: reagentc.exe | |
| try { | |
| $winreOutput = & reagentc /info 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "[2/8] WinRE status check: FAILED" -ForegroundColor Red | |
| Write-Warning "reagentc /info returned exit code $LASTEXITCODE" | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| $winreOutputStr = $winreOutput -join "`n" | |
| # Make this check language agnostic | |
| if ($winreOutputStr -match "[::]\s*Enabled\b") { | |
| Write-Host "[2/8] WinRE status: Enabled" -ForegroundColor Green | |
| } else { | |
| Write-Host "[2/8] WinRE status: NOT ENABLED. Exiting." -ForegroundColor Green | |
| Set-NinjaResult -Status "SUCCESS" | |
| exit 0 | |
| } | |
| } catch { | |
| Write-Warning "Error checking WinRE status: $_" | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| # 3. Mount WinRE Image | |
| # PS Version: All | Admin: Yes | System Requirements: reagentc.exe | |
| try { | |
| if (-not (Test-Path $MountPath)) { | |
| New-Item -ItemType Directory -Path $MountPath -Force | Out-Null | |
| $mountCreated = $true | |
| Write-Host "[3/8] Created mount directory: $MountPath" | |
| } else { | |
| $existing = Get-ChildItem -Path $MountPath -Force -ErrorAction SilentlyContinue | |
| if ($existing) { | |
| Write-Host "[3/8] Mount WinRE image: FAILED" -ForegroundColor Red | |
| Write-Host " Mount directory $MountPath is not empty." | |
| Write-Host " Clean it or specify a different -MountPath." | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| } | |
| $mountOutput = & reagentc /mountre /path $MountPath 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "[3/8] Mount WinRE image: FAILED" -ForegroundColor Red | |
| Write-Warning "reagentc /mountre output: $mountOutput" | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| $imageMounted = $true | |
| Write-Host "[3/8] WinRE image mounted: $MountPath" -ForegroundColor Green | |
| } catch { | |
| Write-Warning "Error mounting WinRE image: $_" | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| # 4. Load Offline SYSTEM Registry Hive | |
| # PS Version: All | Admin: Yes | System Requirements: reg.exe | |
| try { | |
| # Locate the SYSTEM hive in the mounted WinRE image | |
| $hivePath = $null | |
| $hiveCandidates = @( | |
| "$MountPath\Windows\System32\config\SYSTEM", | |
| "$MountPath\windows\system32\config\SYSTEM" | |
| ) | |
| foreach ($candidate in $hiveCandidates) { | |
| if (Test-Path $candidate) { | |
| $hivePath = $candidate | |
| break | |
| } | |
| } | |
| if (-not $hivePath) { | |
| # Broader search as fallback | |
| $found = Get-ChildItem -Path $MountPath -Recurse -Filter "SYSTEM" -ErrorAction SilentlyContinue | | |
| Where-Object { $_.FullName -match "config\\SYSTEM$" } | | |
| Select-Object -First 1 | |
| if ($found) { | |
| $hivePath = $found.FullName | |
| } | |
| } | |
| if (-not $hivePath) { | |
| Write-Host "[4/8] Load offline hive: FAILED" -ForegroundColor Red | |
| Write-Warning "Cannot locate SYSTEM hive in mounted WinRE image." | |
| # Cleanup: unmount with discard | |
| $null = & reagentc /unmountre /path $MountPath /discard 2>&1 | |
| $imageMounted = $false | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| Write-Host "[4/8] Found SYSTEM hive: $hivePath" | |
| $loadOutput = & reg load "HKLM\$HiveName" $hivePath 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "[4/8] Load offline hive: FAILED" -ForegroundColor Red | |
| Write-Warning "reg load output: $loadOutput" | |
| # Cleanup: unmount with discard | |
| $null = & reagentc /unmountre /path $MountPath /discard 2>&1 | |
| $imageMounted = $false | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| $hiveLoaded = $true | |
| Write-Host "[4/8] Hive loaded as HKLM\$HiveName" -ForegroundColor Green | |
| } catch { | |
| Write-Warning "Error loading offline hive: $_" | |
| if ($imageMounted) { | |
| $null = & reagentc /unmountre /path $MountPath /discard 2>&1 | |
| $imageMounted = $false | |
| } | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| # 5. Read BootExecute and Remove autofstx.exe | |
| # PS Version: All | Admin: Yes | System Requirements: None | |
| try { | |
| # Determine active ControlSets from Select key | |
| $selectPath = "Registry::HKEY_LOCAL_MACHINE\$HiveName\Select" | |
| $selectProps = Get-ItemProperty -Path $selectPath -ErrorAction SilentlyContinue | |
| if ($selectProps -and $selectProps.Current) { | |
| $csNumbers = @($selectProps.Current) | |
| if ($selectProps.Default -and $selectProps.Default -ne $selectProps.Current) { | |
| $csNumbers += $selectProps.Default | |
| } | |
| $controlSets = $csNumbers | ForEach-Object { "ControlSet{0:D3}" -f [int]$_ } | |
| Write-Host "[5/8] Active ControlSets (from Select key): $($controlSets -join ', ')" | |
| } else { | |
| $controlSets = @("ControlSet001") | |
| Write-Host "[5/8] Select key not readable, falling back to ControlSet001" | |
| } | |
| # Remediate all relevant ControlSets | |
| $foundEntry = $false | |
| foreach ($cs in $controlSets) { | |
| $regPath = "Registry::HKEY_LOCAL_MACHINE\$HiveName\$cs\Control\Session Manager" | |
| $testResult = Get-ItemProperty -Path $regPath -Name "BootExecute" -ErrorAction SilentlyContinue | |
| if (-not $testResult) { | |
| Write-Host " $cs\Session Manager\BootExecute: not found, skipping" | |
| continue | |
| } | |
| $currentValue = $testResult.BootExecute | |
| if (-not $currentValue) { | |
| Write-Host " $cs BootExecute: (empty)" | |
| continue | |
| } | |
| # Remove matching entry (case-insensitive) | |
| $updatedValue = @($currentValue | Where-Object { | |
| $_ -and | |
| ($_ -ne $EntryToRemove) -and | |
| ($_ -notmatch "^\s*$([regex]::Escape($EntryToRemove))\s*$") | |
| }) | |
| if ($updatedValue.Count -eq @($currentValue).Count) { | |
| Write-Host " $cs BootExecute: '$EntryToRemove' not present" | |
| continue | |
| } | |
| # Write updated REG_MULTI_SZ back | |
| Set-ItemProperty -Path $regPath -Name "BootExecute" -Value $updatedValue | |
| $foundEntry = $true | |
| Write-Host " $cs BootExecute: removed '$EntryToRemove'" -ForegroundColor Green | |
| # Verify the write | |
| $verifyValue = (Get-ItemProperty -Path $regPath -Name "BootExecute" -ErrorAction Stop).BootExecute | |
| Write-Host " $cs verification: $($verifyValue -join '; ')" | |
| } | |
| if (-not $foundEntry) { | |
| Write-Host "[5/8] '$EntryToRemove' not found in any active ControlSet. No changes needed." -ForegroundColor Green | |
| # Cleanup: unload hive, unmount with discard | |
| [gc]::Collect() | |
| Start-Sleep -Seconds 1 | |
| $null = & reg unload "HKLM\$HiveName" 2>&1 | |
| $hiveLoaded = $false | |
| $null = & reagentc /unmountre /path $MountPath /discard 2>&1 | |
| $imageMounted = $false | |
| if ($mountCreated) { Remove-Item -Path $MountPath -Recurse -Force -ErrorAction SilentlyContinue } | |
| Set-NinjaResult -Status "SUCCESS" | |
| exit 0 | |
| } | |
| $changesMade = $true | |
| Write-Host "[5/8] Removed '$EntryToRemove' from BootExecute" -ForegroundColor Green | |
| } catch { | |
| Write-Warning "Error modifying BootExecute: $_" | |
| # Cleanup: unload hive, unmount with discard | |
| [gc]::Collect() | |
| Start-Sleep -Seconds 2 | |
| if ($hiveLoaded) { | |
| $null = & reg unload "HKLM\$HiveName" 2>&1 | |
| $hiveLoaded = $false | |
| } | |
| if ($imageMounted) { | |
| $null = & reagentc /unmountre /path $MountPath /discard 2>&1 | |
| $imageMounted = $false | |
| } | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| # 6. Unload Offline Registry Hive | |
| # PS Version: All | Admin: Yes | System Requirements: reg.exe | |
| try { | |
| [gc]::Collect() | |
| Start-Sleep -Seconds 2 | |
| $unloadOutput = & reg unload "HKLM\$HiveName" 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Warning "First unload attempt failed, retrying..." | |
| [gc]::Collect() | |
| Start-Sleep -Seconds 3 | |
| $unloadOutput = & reg unload "HKLM\$HiveName" 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "[6/8] Unload hive: FAILED" -ForegroundColor Red | |
| Write-Warning "reg unload output: $unloadOutput" | |
| Write-Host " Close any Registry Editor windows and retry." | |
| # Try to unmount with discard to avoid leaving image mounted | |
| $null = & reagentc /unmountre /path $MountPath /discard 2>&1 | |
| $imageMounted = $false | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| } | |
| $hiveLoaded = $false | |
| Write-Host "[6/8] Offline hive unloaded" -ForegroundColor Green | |
| } catch { | |
| Write-Warning "Error unloading hive: $_" | |
| $null = & reagentc /unmountre /path $MountPath /discard 2>&1 | |
| $imageMounted = $false | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| # 7. Unmount WinRE Image with Commit | |
| # PS Version: All | Admin: Yes | System Requirements: reagentc.exe | |
| try { | |
| if ($changesMade) { | |
| $unmountOutput = & reagentc /unmountre /path $MountPath /commit 2>&1 | |
| } else { | |
| $unmountOutput = & reagentc /unmountre /path $MountPath /discard 2>&1 | |
| } | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "[7/8] Unmount WinRE: FAILED" -ForegroundColor Red | |
| Write-Warning "reagentc /unmountre output: $unmountOutput" | |
| # Attempt discard as fallback | |
| if ($changesMade) { | |
| Write-Host " Attempting discard to avoid leaving image in broken state..." | |
| $null = & reagentc /unmountre /path $MountPath /discard 2>&1 | |
| } | |
| $imageMounted = $false | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| $imageMounted = $false | |
| if ($changesMade) { | |
| Write-Host "[7/8] WinRE image unmounted and committed" -ForegroundColor Green | |
| } else { | |
| Write-Host "[7/8] WinRE image unmounted (no changes)" -ForegroundColor Green | |
| } | |
| } catch { | |
| Write-Warning "Error unmounting WinRE image: $_" | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| # 8. Re-seal WinRE (Disable + Enable Cycle for BitLocker Trust Chain) | |
| # PS Version: All | Admin: Yes | System Requirements: reagentc.exe, BitLocker | |
| try { | |
| if (-not $changesMade) { | |
| Write-Host "[8/8] Re-seal WinRE: Skipped (no changes made)" -ForegroundColor Green | |
| } else { | |
| $disableOutput = & reagentc /disable 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Warning "reagentc /disable returned non-zero: $disableOutput" | |
| } else { | |
| Write-Host " WinRE disabled." | |
| } | |
| $enableOutput = & reagentc /enable 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "[8/8] Re-seal WinRE: FAILED" -ForegroundColor Red | |
| Write-Warning "reagentc /enable output: $enableOutput" | |
| Write-Host " WinRE may need manual recovery. Run: reagentc /enable" | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| Write-Host "[8/8] WinRE re-sealed (disable + enable cycle)" -ForegroundColor Green | |
| } | |
| } catch { | |
| Write-Warning "Error during WinRE re-seal: $_" | |
| Write-Host " Run manually: reagentc /enable" | |
| Set-NinjaResult -Status "FAILED" | |
| exit 1 | |
| } | |
| Write-Host "" | |
| Write-Host " COMPLETE: '$EntryToRemove' removed from WinRE BootExecute" -ForegroundColor Green | |
| # Cleanup: remove mount directory if we created it | |
| if ($mountCreated -and (Test-Path $MountPath)) { | |
| Remove-Item -Path $MountPath -Recurse -Force -ErrorAction SilentlyContinue | |
| } | |
| Set-NinjaResult -Status "SUCCESS" | |
| exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment