From 96ca0fd96751b0a8defe31ab346ddcca42fff2d4 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Fri, 19 Jun 2026 13:15:00 -0400 Subject: [PATCH] Fix unenroll Windows instructions (#47725) - @noahtalerman: For Windows, I think we want to squeeze turning off MDM and uninstalling fleetd into one script. - Why? Because Fleet automatically turns on Windows MDM, two scripts means Fleet could beat the IT admin and turn MDM back on before they uninstall fleetd. ## Summary by CodeRabbit * **New Features** * Windows fleetd uninstall now proactively detects and disables MDM enrollment before removal to help ensure a cleaner device unenrollment. * **Bug Fixes** * Improved uninstall error reporting by surfacing the underlying failure message and exiting with a clear non-zero code. * **Chores** * Updated fleet testing and workstation configurations: removed the Windows uninstall/MDM-related steps from QA and workstation controls, and added new Windows security/setup scripts plus additional cross-platform post-install and extension installation tasks. --------- Co-authored-by: Steven Palmesano <3100993+spalmesano0@users.noreply.github.com> --- articles/enroll-hosts.md | 10 +- articles/how-to-uninstall-fleetd.md | 4 +- articles/windows-mdm-setup.md | 2 +- .../scripts/uninstall-fleetd-windows.ps1 | 97 ++++++++++++++++--- it-and-security/fleets/testing-and-qa.yml | 1 - it-and-security/fleets/workstations.yml | 2 - .../lib/windows/scripts/turn-off-mdm.ps1 | 91 ----------------- 7 files changed, 92 insertions(+), 115 deletions(-) rename {it-and-security/lib => docs/solutions}/windows/scripts/uninstall-fleetd-windows.ps1 (60%) delete mode 100644 it-and-security/lib/windows/scripts/turn-off-mdm.ps1 diff --git a/articles/enroll-hosts.md b/articles/enroll-hosts.md index e39e192617..1c186218ec 100644 --- a/articles/enroll-hosts.md +++ b/articles/enroll-hosts.md @@ -160,12 +160,12 @@ In the Google Admin console: 1. Determine if your host has MDM features turned on by looking at the **MDM status** on the host's **Host details** page. -2. If MDM is turned on, for macOS, Windows, iOS/iPadOS, and Android hosts: - - For macOS hosts, select **Actions > Turn off MDM** on the host's details page to turn MDM off. - - For Windows hosts, download the [turn off MDM script](https://github.com/fleetdm/fleet/blob/main/it-and-security/lib/windows/scripts/turn-off-mdm.ps1), add it to the host's fleet on the **Scripts** page in Fleet, and run the script via **Actions > Run script** on the host's details page. - - For iOS/iPadOS and Android hosts, select **Actions > Unenroll**. +2. If MDM is turned on, turn it off: + - Windows: Skip to step 3 (Uninstall Fleet's agent). + - macOS: On the **Host details** page, select **Actions > Turn off MDM**. + - iOS/iPadOS & Android: On the **Host details** page, select **Actions > Unenroll**. -3. [Uninstall fleetd](https://fleetdm.com/guides/how-to-uninstall-fleetd) for macOS, Windows, and Linux hosts. +3. For macOS, Windows, and Linux hosts, [uninstall Fleet's agent (fleetd)](https://fleetdm.com/guides/how-to-uninstall-fleetd). 4. Select **Actions > Delete** to delete the host from Fleet. diff --git a/articles/how-to-uninstall-fleetd.md b/articles/how-to-uninstall-fleetd.md index 9074401a28..0fd8d0ff41 100644 --- a/articles/how-to-uninstall-fleetd.md +++ b/articles/how-to-uninstall-fleetd.md @@ -18,7 +18,7 @@ To remove fleetd from a Mac: To remove fleetd from a Windows device: -1. Download the [Windows uninstall script](https://github.com/fleetdm/fleet/blob/main/it-and-security/lib/windows/scripts/uninstall-fleetd-windows.ps1). +1. Download the [Windows uninstall script](https://github.com/fleetdm/fleet/blob/main/docs/solutions/windows/scripts/uninstall-fleetd-windows.ps1). This script turns off MDM and uninstalls fleetd. 2. Open **PowerShell** as administrator (right-click and select **Run as administrator**). 3. Navigate to where you saved the script: `cd C:\path\to\your\script` 4. Run the script: `.\uninstall-fleetd-windows.ps1` @@ -41,7 +41,7 @@ To remove fleetd from a Linux device: To remove fleetd from a device through Fleet: -1. Add the uninstall script for [macOS](https://github.com/fleetdm/fleet/blob/main/it-and-security/lib/macos/scripts/uninstall-fleetd-macos.sh), [Windows](https://github.com/fleetdm/fleet/blob/main/it-and-security/lib/windows/scripts/uninstall-fleetd-windows.ps1), or [Linux](https://github.com/fleetdm/fleet/blob/main/it-and-security/lib/linux/scripts/uninstall-fleetd-linux.sh) to Fleet as a script. +1. Add the uninstall script for [macOS](https://github.com/fleetdm/fleet/blob/main/it-and-security/lib/macos/scripts/uninstall-fleetd-macos.sh), [Windows](https://github.com/fleetdm/fleet/blob/main/docs/solutions/windows/scripts/uninstall-fleetd-windows.ps1), or [Linux](https://github.com/fleetdm/fleet/blob/main/it-and-security/lib/linux/scripts/uninstall-fleetd-linux.sh) to Fleet as a script. 2. Go to the device's **Host details** page. 3. Select **Actions > Run script** and choose the uninstall script. diff --git a/articles/windows-mdm-setup.md b/articles/windows-mdm-setup.md index 10d414c6ea..d00194a624 100644 --- a/articles/windows-mdm-setup.md +++ b/articles/windows-mdm-setup.md @@ -261,7 +261,7 @@ The Autopilot service may need a few minutes to sync after the device record cle ## Turn off Windows MDM -1. Turn off MDM for each host by running [this script](https://github.com/fleetdm/fleet/blob/main/it-and-security/lib/windows/scripts/turn-off-mdm.ps1) from Fleet on all your Windows hosts. +1. Turn off MDM for each host by running [this script](https://github.com/fleetdm/fleet/blob/main/docs/solutions/windows/scripts/uninstall-fleetd-windows.ps1) from Fleet on all your Windows hosts. Note that this script will also remove fleetd from the hosts. 2. Head to **Settings > Integrations > MDM**. diff --git a/it-and-security/lib/windows/scripts/uninstall-fleetd-windows.ps1 b/docs/solutions/windows/scripts/uninstall-fleetd-windows.ps1 similarity index 60% rename from it-and-security/lib/windows/scripts/uninstall-fleetd-windows.ps1 rename to docs/solutions/windows/scripts/uninstall-fleetd-windows.ps1 index 405df6f551..bce28e4b6f 100644 --- a/it-and-security/lib/windows/scripts/uninstall-fleetd-windows.ps1 +++ b/docs/solutions/windows/scripts/uninstall-fleetd-windows.ps1 @@ -1,4 +1,22 @@ -# Please don't delete. This script is referenced in the guide here: https://fleetdm.com/guides/how-to-uninstall-fleetd +# Please don't delete. This script is referenced in the guides here: +# - https://fleetdm.com/guides/windows-mdm-setup#turn-off-windows-mdm +# - https://fleetdm.com/guides/how-to-uninstall-fleetd + +Add-Type -TypeDefinition @" +using System; +using System.Runtime.InteropServices; + +public class MdmRegistration +{ + [DllImport("mdmregistration.dll", SetLastError = true)] + public static extern int UnregisterDeviceWithManagement(IntPtr pDeviceID); + + public static int UnregisterDevice() + { + return UnregisterDeviceWithManagement(IntPtr.Zero); + } +} +"@ -Language CSharp function Test-Administrator { @@ -66,7 +84,7 @@ function Force-Remove-Orbit { return } } - + # Write success log "Fleetd successfully removed at $(Get-Date)" | Out-File -Append -FilePath "$env:TEMP\fleet_remove_log.txt" } @@ -89,18 +107,16 @@ function Main { Exit -1 } - Write-Host "About to uninstall fleetd..." - if ($args[0] -eq "remove") { # "remove" is received as argument to the script when called as the # sub-process that will actually remove the fleet agent. # Log the start of removal process "Starting removal process at $(Get-Date)" | Out-File -Append -FilePath "$env:TEMP\fleet_remove_log.txt" - + # sleep to give time to fleetd to send the script results to Fleet Start-Sleep -Seconds 20 - + if (Force-Remove-Orbit) { Write-Host "fleetd was uninstalled." Exit 0 @@ -109,26 +125,81 @@ function Main { Exit -1 } } else { + # Turn off MDM first so Fleet cannot re-enable it before fleetd is removed. + + # Check 1: Fleet-specific enrollment (ProviderID + EnrollmentState) + $enrollmentKey = Get-Item -Path HKLM:\SOFTWARE\Microsoft\Enrollments\* -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object {$_.ProviderID -eq 'Fleet'} | Where-Object {$_.EnrollmentState -match '1|3|6|13'} + $mdmEnrolled = $null -ne $enrollmentKey + + # Check 2: fallback via DiscoveryServiceFullURL + $enrollmentsPath = "HKLM:\SOFTWARE\Microsoft\Enrollments" + if (-not $mdmEnrolled) { + if (Test-Path $enrollmentsPath) { + $enrollmentKeys = Get-ChildItem -Path $enrollmentsPath -ErrorAction SilentlyContinue + foreach ($key in $enrollmentKeys) { + if ($null -ne (Get-ItemProperty -Path $key.PSPath -Name "DiscoveryServiceFullURL" -ErrorAction SilentlyContinue)) { + $mdmEnrolled = $true + break + } + } + } + } + + if ($mdmEnrolled) { + $result = [MdmRegistration]::UnregisterDevice() + + if ($result -ne 0) { + throw "UnregisterDeviceWithManagement failed with error code: $result" + } + + Write-Host "Device unregistration called successfully." + + $clearedCount = 0 + + if (Test-Path $enrollmentsPath) { + $enrollmentKeys = Get-ChildItem -Path $enrollmentsPath -ErrorAction SilentlyContinue + + foreach ($key in $enrollmentKeys) { + if ($null -ne (Get-ItemProperty -Path $key.PSPath -Name "DiscoveryServiceFullURL" -ErrorAction SilentlyContinue)) { + try { + Remove-ItemProperty -Path $key.PSPath -Name "DiscoveryServiceFullURL" -ErrorAction Stop + $clearedCount++ + Write-Host "Cleared DiscoveryServiceFullURL from enrollment key: $($key.PSChildName)" + } catch { + Write-Warning "Failed to clear DiscoveryServiceFullURL from $($key.PSChildName): $_" + } + } + } + } + + if ($clearedCount -gt 0) { + Write-Host "Cleared DiscoveryServiceFullURL from $clearedCount enrollment key(s)." + } else { + Write-Host "Turning off MDM completed. The UnregisterDeviceWithManagement API automatically cleared the registry values." + } + } else { + Write-Host "MDM is not turned on. Skipping MDM unregistration." + } + # when this script is executed from fleetd, it does not immediately # remove the agent. Instead, it starts a new detached process that # will do the actual removal. - + Write-Host "Removing fleetd, system will be unenrolled in 20 seconds..." Write-Host "Executing detached child process" - + $execName = $MyInvocation.ScriptName $proc = Start-Process -PassThru -FilePath "powershell" -WindowStyle Hidden -ArgumentList "-MTA", "-ExecutionPolicy", "Bypass", "-File", "`"$execName`"", "remove" - + # Log the process ID "Started removal process with ID: $($proc.Id) at $(Get-Date)" | Out-File -Append -FilePath "$env:TEMP\fleet_remove_log.txt" - + Start-Sleep -Seconds 5 # give time to process to start running Write-Host "Removal process started: $($proc.Id)." } } catch { - Write-Host "Error: Entry point" - Write-Host "$(Resolve-Error-Detailed)" - Exit -1 + Write-Error "Error running fleetd unenrollment script: $_" + exit 1 } } diff --git a/it-and-security/fleets/testing-and-qa.yml b/it-and-security/fleets/testing-and-qa.yml index 0458794afe..1c272a360d 100644 --- a/it-and-security/fleets/testing-and-qa.yml +++ b/it-and-security/fleets/testing-and-qa.yml @@ -83,7 +83,6 @@ controls: # macOS scripts - path: ../lib/macos/scripts/uninstall-fleetd-macos.sh # Windows scripts - - path: ../lib/windows/scripts/uninstall-fleetd-windows.ps1 # Linux scripts - path: ../lib/linux/scripts/uninstall-fleetd-linux.sh - path: ../lib/linux/scripts/install-fleet-desktop-required-extension.sh diff --git a/it-and-security/fleets/workstations.yml b/it-and-security/fleets/workstations.yml index da768e7238..d38bbf8032 100644 --- a/it-and-security/fleets/workstations.yml +++ b/it-and-security/fleets/workstations.yml @@ -148,8 +148,6 @@ controls: grace_period_days: 2 scripts: - path: ../lib/macos/scripts/uninstall-fleetd-macos.sh - - path: ../lib/windows/scripts/uninstall-fleetd-windows.ps1 - - path: ../lib/windows/scripts/turn-off-mdm.ps1 - path: ../lib/windows/scripts/enable-ms-defender.ps1 - path: ../lib/windows/scripts/create-admin-user.ps1 - path: ../lib/linux/scripts/uninstall-fleetd-linux.sh diff --git a/it-and-security/lib/windows/scripts/turn-off-mdm.ps1 b/it-and-security/lib/windows/scripts/turn-off-mdm.ps1 deleted file mode 100644 index daf3f33448..0000000000 --- a/it-and-security/lib/windows/scripts/turn-off-mdm.ps1 +++ /dev/null @@ -1,91 +0,0 @@ -# Please don't delete. This script is referenced in the guide here: https://fleetdm.com/guides/windows-mdm-setup#turn-off-windows-mdm - -Add-Type -TypeDefinition @" -using System; -using System.Runtime.InteropServices; - -public class MdmRegistration -{ - [DllImport("mdmregistration.dll", SetLastError = true)] - public static extern int UnregisterDeviceWithManagement(IntPtr pDeviceID); - - public static int UnregisterDevice() - { - return UnregisterDeviceWithManagement(IntPtr.Zero); - } -} -"@ -Language CSharp - -try { - # Step 1: Check for DiscoveryServiceFullURL values before unregistering - # This helps us provide clearer output about what happened - $enrollmentsPath = "HKLM:\SOFTWARE\Microsoft\Enrollments" - $foundBeforeUnregister = $false - - if (Test-Path $enrollmentsPath) { - $enrollmentKeys = Get-ChildItem -Path $enrollmentsPath -ErrorAction SilentlyContinue - - foreach ($key in $enrollmentKeys) { - $upnPath = Join-Path $key.PSPath "UPN" - $discoveryUrlPath = Join-Path $key.PSPath "DiscoveryServiceFullURL" - - if (Test-Path $upnPath) { - if (Test-Path $discoveryUrlPath) { - $foundBeforeUnregister = $true - break - } - } - } - } - - # Step 2: Unregister the device from MDM using the Windows API - $result = [MdmRegistration]::UnregisterDevice() - - if ($result -ne 0) { - throw "UnregisterDeviceWithManagement failed with error code: $result" - } - - Write-Host "Device unregistration called successfully." - - # Step 3: Clear any remaining DiscoveryServiceFullURL registry values to ensure Fleet detects - # the device as unenrolled on the next refetch. The UnregisterDeviceWithManagement API - # may have already cleared these values, but we check and clear any remaining ones to be safe. - $clearedCount = 0 - - if (Test-Path $enrollmentsPath) { - $enrollmentKeys = Get-ChildItem -Path $enrollmentsPath -ErrorAction SilentlyContinue - - foreach ($key in $enrollmentKeys) { - # Only clear DiscoveryServiceFullURL from enrollment keys that have a UPN - # (these are the ones Fleet's query checks). This matches Fleet's query logic - # which filters by entries with UPN values. - $upnPath = Join-Path $key.PSPath "UPN" - $discoveryUrlPath = Join-Path $key.PSPath "DiscoveryServiceFullURL" - - if (Test-Path $upnPath) { - if (Test-Path $discoveryUrlPath) { - try { - Remove-ItemProperty -Path $key.PSPath -Name "DiscoveryServiceFullURL" -ErrorAction Stop - $clearedCount++ - Write-Host "Cleared DiscoveryServiceFullURL from enrollment key: $($key.PSChildName)" - } catch { - Write-Warning "Failed to clear DiscoveryServiceFullURL from $($key.PSChildName): $_" - } - } - } - } - } - - # Provide clearer output based on what we found - if ($clearedCount -gt 0) { - Write-Host "Cleared DiscoveryServiceFullURL from $clearedCount enrollment key(s). Fleet will detect the device as unenrolled on the next refetch." - } elseif ($foundBeforeUnregister) { - Write-Host "MDM unregistration completed. The UnregisterDeviceWithManagement API automatically cleared the registry values." - Write-Host "Fleet will detect the device as unenrolled on the next refetch." - } else { - Write-Host "MDM unregistration completed. No DiscoveryServiceFullURL registry values were found (device was not enrolled or values were already cleared)." - } -} catch { - Write-Error "Error calling UnregisterDeviceWithManagement: $_" - exit 1 -}