Guide Expired May 2025

Dell Image Assist - Image Creation Process

Creating a golden image for deployment in a production enviroment.

projects Windows Powershell Batch
README.md

Practical, field-tested guide for building and capturing a Windows .wim image in Hyper-V.


Purpose and Scope

This document shows the workflow I use to build a clean, reusable Windows image for deployment using Dell Image Assist

Portfolio Context (Why This Work Matters)

This was handed off to me during team restructuring. My manager asked me to learn the existing process, then improve it where possible.

At the time, we were not yet on SCCM, so imaging had to be done with the tools and constraints we already had:

  • Network shares for image storage/distribution
  • Dell Image Assist for capture (licensed/approved tool at the time)

I built this process into a repeatable standard for our IT team and documented it end-to-end.

Ownership and Impact

  • Took ownership of imaging workflow and process quality
  • Reduced the internal golden image from 20+ GB to under 10 GB
  • Standardized build and cleanup steps so technicians follow one consistent method
  • Created formal documentation where none previously existed
  • Started collaboration with the networking team to transition imaging to SCCM for a more hands-off deployment model

Prerequisites

  • Approved enterprise Windows ISO
  • Hyper-V host with admin access
  • Required automation scripts (same folder):
    • Golden-prep.bat (launcher script)
    • Remove-Bloatware.ps1 (called by batch script)
  • Local admin credentials for the VM

Table of Contents

[[#Purpose and Scope]] ├── [[#Portfolio Context (Why This Work Matters)]] ├── [[#Ownership and Impact]] ├── [[#Audience]] └── [[#Prerequisites]] [[#Process Overview]] [[#1. VM Creation]] ├── [[#1.1 Windows ISO]] ├── [[#1.2 Hyper-V Manager Configuration]] ├── [[#1.3 Hyper-V Security and Integration Settings]] └── [[#1.4 Start the VM and Install Windows]] [[#2. Image Preparation]] ├── [[#2.1 Default Admin Account]] └── [[#2.2 Baseline Configuration and Installs]] [[#3. Image Cleanup]] ├── [[#3.1 Run Scripted App Cleanup]] └── [[#3.2 System Cleanup Commands (PowerShell as Admin)]] [[#4. VHDX Merge and Checkpoint Cleanup]] └── [[#4.1 Pre-Capture QA (Before Sysprep/Image Assist)|4.1 Pre-Capture QA]] [[#5. Dell Image Assist Capture]] ├── [[#5.1 Known Issues]] └── [[#5.2 Launch Settings]] [[#6. Image Rollback]] [[#7. Automation Scripts (Batch + PowerShell)]] ├── [[#7.1 Golden-prep.bat (Launcher)]] └── [[#7.2 Remove-Bloatware.ps1 (Cleanup Script)]] [[#Validation Checklist]] [[#Change Log]]


Process Overview

  1. VM creation
  2. Image preparation
  3. Image cleanup
  4. VHDX merge/checkpoint cleanup
  5. Dell Image Assist capture
  6. Rollback strategy

1. VM Creation

1.1 Windows ISO

Use the approved Windows ISO for your current deployment cycle.

1.2 Hyper-V Manager Configuration

Create a new virtual machine with these settings:

  • Generation 2
  • 4096 MB RAM (non-dynamic)
  • Network adapter: Not Connected
  • Enterprise Windows image

Set these values first, then leave the VM powered off.

1.3 Hyper-V Security and Integration Settings

Security

  • Enable Secure Boot
  • Enable Trusted Platform Module (TPM)

Integration Services

  • Enable Guest Services (for easier file transfer into the VM)

1.4 Start the VM and Install Windows

  1. Boot from the CD drive and press Spacebar when prompted.
  2. Complete the Windows 11 setup flow.
  3. Wait for installation to finish.

[!info] If setup requires a network bypass (OOBE)

  • Press Shift+F10, then run OOBE\BYPASSNRO
  • Alternative local user path: start ms-cxh:localonly

2. Image Preparation

2.1 Default Admin Account

  1. Create a local account.
    • Username: oit
    • Password: xxxxxx
  2. Turn off all privacy options.
  3. Use monkey for security question answers to keep builds consistent.
  4. Sign in to the oit account.

2.2 Baseline Configuration and Installs

  1. Right-click the taskbar and disable:
    • Task View
    • Widgets
    • Copilot
  2. Unpin unnecessary taskbar items.
  3. Set desktop wallpaper.
  4. Run Golden-prep.bat for baseline automation (apps, policies, desktop icon setup, and cleanup trigger).
STOP - Create a snapshot

Take a checkpoint here so you have a clean rollback point before updates and extra installs.

If updates or installs go sideways, this saves hours of rebuild time.

  1. Reconnect network access to the VM.
  2. Run Windows Update until no additional updates are available.
  3. Reboot as required.
  4. Confirm www.brookdalecc.edu is set as the startup page (scripted policy should handle this).
  5. Install any additional baseline apps not already handled by script automation.
  6. Restart the VM.

3. Image Cleanup

Disconnect from the internet

Disconnect the VM from network access before cleanup.

This prevents background sync/services from recreating files while you are trying to clean the image.

3.1 Run Scripted App Cleanup

Run Remove-Bloatware.ps1 to remove bundled apps and leftover components.

If you run Golden-prep.bat end-to-end, this cleanup script is called automatically in the final stage.

Current cleanup targets include:

  • Teams 2.0 (MSIX)
  • Teams Machine-Wide Installer
  • Teams provisioned packages
  • Teams Meeting Add-in
  • Microsoft.BingNews
  • Microsoft.Todos
  • MicrosoftWindowsCommunicationsApps (Mail and Calendar)
  • Outlook
  • MicrosoftOfficeHub (Office stub)
  • Office
  • Related installation directories
  • User data folders
  • Startup entries
  • Uninstall registry entries
  • App data and cache files
  • Copilot auto-start entries (app remains available)

[!important] Why this matters Removing these components keeps the base image lean and reduces post-deployment cleanup tickets.

3.2 System Cleanup Commands (PowerShell as Admin)

  1. Check whether BitLocker is enabled on C:. If it is, disable it and wait for full decryption.
manage-bde -status C:

If encrypted, run:

manage-bde -off C:

Verify decryption is complete by running manage-bde -status C: again.

  1. Disable hibernation.
powercfg /h off
  1. Clear event logs.
Get-WinEvent -ListLog * | Where-Object {$_.IsEnabled -and $_.RecordCount -gt 0} | ForEach-Object { Clear-EventLog -LogName $_.LogName }

Alternative in Command Prompt (Admin):

for /F "tokens=*" %L IN ('wevtutil el') DO wevtutil cl "%L"
  1. Delete temporary files.

    • C:\Windows\Temp
    • C:\Users\oit\AppData\Local\Temp (or the active profile temp folder)
  2. Run DISM component cleanup.

dism /online /Cleanup-Image /StartComponentCleanup /ResetBase

[!warning] DISM /ResetBase note This makes component cleanup permanent for installed updates. Use it only after patching is complete.

Restart the VM after this step completes.

  1. Configure Disk Cleanup options.
cleanmgr /sageset:1
Note

In the Disk Cleanup settings window, select all options for maximum cleanup.

  1. Run Disk Cleanup with saved settings.
cleanmgr /sagerun:1
  1. Zero out free space to improve VHDX compaction.
cipher /w:C:

Wait for completion; this step can take a while.

High-impact step

Do not interrupt cipher /w:C: once started. Stopping mid-run can leave compaction results inconsistent.

STOP - Create snapshot and export

This is your golden pre-sysprep checkpoint.


4. VHDX Merge and Checkpoint Cleanup

In Hyper-V, select the VM currently being prepared.

To merge the final disk state, remove checkpoints from newest to oldest.

Checkpoint tree example:

Automatic checkpoint (SmallV1)
  └── "Pre-Sysprep" checkpoint
      └── "Now" (current state of disk after sysprep and shutdown)
  1. Select the latest snapshot (for example, Pre-Sysprep) and delete it.
  2. Wait for deletion to fully complete.
  3. Select the next checkpoint (for example, Automatic Checkpoint) and delete it.

After all checkpoints are deleted, the virtual hard disk is merged into the latest state.

Checkpoint deletion safety

Confirm the VM name and checkpoint chain before deleting. Removing the wrong chain can destroy your rollback path.


4.1 Pre-Capture QA (Before Sysprep/Image Assist)

Run this quick gate before moving into sysprep/capture:

  • VM baseline settings still match standard (Gen 2, static RAM, Secure Boot, TPM)
  • Updates complete with no pending reboot
  • Cleanup script completed and manual cleanup steps are done
  • BitLocker is confirmed Off on C:
  • Golden pre-sysprep snapshot exists and export is complete
  • Network is disconnected for capture stage
  • C:\Windows\Panther\unattend.xml exists and is ready to import

[!important] Release gate If any item above is not complete, stop and fix it first. Capturing early usually creates repeat work later.


5. Dell Image Assist Capture

5.1 Known Issues

OS media type unknown

Update ImageAssist.exe.Config:

  1. Locate the file in the Dell Image Assist install directory.
  2. Change: <add key="SkipApplicationLaunchValidations" value="False" /> to: <add key="SkipApplicationLaunchValidations" value="True" />

System cannot be unjoined from a domain

Delete C:\Windows\debug\Netsetup.log, then rerun Dell Image Assist.

5.2 Launch Settings

  • Create an ISO image: No
  • Add SupportAssist: No
  • Unattend XML file: Import
  • Path: C:\Windows\Panther\unattend.xml

6. Image Rollback

If capture fails or post-deployment issues are found, restore the latest golden pre-sysprep checkpoint and rerun from that stage.


7. Automation Scripts (Batch + PowerShell)

Script integration

Golden-prep.bat is the primary launcher for this workflow. It applies baseline configuration, then calls Remove-Bloatware.ps1.

Source of truth

These scripts are maintained in Scripts.md. They are embedded here for portfolio review.

7.1 Golden-prep.bat (Launcher)

@echo off
setlocal enabledelayedexpansion

:: ------------------------------------------------------------------------------
:: # INSTALL BROWSERS (Chrome and Firefox)

echo.
echo ============================================================
echo  Installing Google Chrome and Mozilla Firefox
echo ============================================================
echo.

set "SCRIPT_DIR=%~dp0"

echo [1/2] Installing Google Chrome...
if exist "%SCRIPT_DIR%googlechromestandaloneenterprise64.msi" (
    msiexec /i "%SCRIPT_DIR%googlechromestandaloneenterprise64.msi" /qn /norestart
    if !errorLevel! equ 0 (
        echo   [OK] Google Chrome installed successfully
    ) else (
        echo   [WARN] Google Chrome installation returned error code !errorLevel!
    )
) else (
    echo   [SKIP] Chrome MSI not found
)

echo.
echo [2/2] Installing Mozilla Firefox...
if exist "%SCRIPT_DIR%Firefox Setup 138.0.3.msi" (
    msiexec /i "%SCRIPT_DIR%Firefox Setup 138.0.3.msi" /qn /norestart
    if !errorLevel! equ 0 (
        echo   [OK] Mozilla Firefox installed successfully
    ) else (
        echo   [WARN] Mozilla Firefox installation returned error code !errorLevel!
    )
) else (
    echo   [SKIP] Firefox MSI not found
)

echo.

:: ------------------------------------------------------------------------------
:: # INSTALL LATEST POWERSHELL

echo.
echo ============================================================
echo  Installing Latest PowerShell via Winget
echo ============================================================
echo.

echo Installing Microsoft PowerShell...
winget install --id Microsoft.PowerShell --source winget --accept-package-agreements --accept-source-agreements --silent
if %errorLevel% equ 0 (
    echo   [OK] PowerShell installed successfully
) else (
    echo   [WARN] PowerShell installation returned error code %errorLevel%
)

echo.

:: ------------------------------------------------------------------------------
:: # REMOVE MICROSOFT STORE

echo.
echo ============================================================
echo  Unpinning Microsoft Store from Taskbar (All Users)
echo ============================================================
echo.
echo Using Microsoft's official policy: NoPinningStoreToTaskbar
echo.

set "RegKeyPath=Software\Policies\Microsoft\Windows\Explorer"
set "ValueName=NoPinningStoreToTaskbar"
set "ValueData=1"

echo [1/3] Applying policy to currently loaded user profiles...
echo.

REM Apply to all currently loaded user hives in HKEY_USERS
for /f "tokens=*" %%S in ('reg query HKU ^| findstr /r "S-1-5-21-"') do (
    set "UserHive=%%S"
    set "UserKeyPath=!UserHive!\%RegKeyPath%"
    
    REM Create the registry key if it doesn't exist
    reg add "!UserKeyPath!" /f >nul 2>&1
    
    REM Set the policy value
    reg add "!UserKeyPath!" /v "%ValueName%" /t REG_DWORD /d %ValueData% /f >nul 2>&1
    
    if !errorLevel! equ 0 (
        echo   [OK] Applied to !UserHive!
    ) else (
        echo   [WARN] Could not apply to !UserHive!
    )
)

echo.
echo [2/3] Applying policy to offline user profiles...
echo.

REM Process all user profile directories
for /d %%U in (C:\Users\*) do (
    set "UserFolder=%%~nxU"
    set "NtUserDat=%%U\NTUSER.DAT"
    
    REM Skip system profiles
    if /i not "!UserFolder!"=="Public" (
        if /i not "!UserFolder!"=="Default" (
            if /i not "!UserFolder!"=="Default User" (
                if exist "!NtUserDat!" (
                    REM Check if this hive is already loaded
                    reg query "HKU\!UserFolder!" >nul 2>&1
                    if errorlevel 1 (
                        echo   Processing: !UserFolder!
                        
                        REM Load the user hive temporarily
                        reg load "HKU\TempHive_!UserFolder!" "!NtUserDat!" >nul 2>&1
                        
                        if !errorLevel! equ 0 (
                            REM Create the policy key
                            reg add "HKU\TempHive_!UserFolder!\%RegKeyPath%" /f >nul 2>&1
                            
                            REM Set the policy value
                            reg add "HKU\TempHive_!UserFolder!\%RegKeyPath%" /v "%ValueName%" /t REG_DWORD /d %ValueData% /f >nul 2>&1
                            
                            REM Unload the hive
                            reg unload "HKU\TempHive_!UserFolder!" >nul 2>&1
                            
                            echo   [OK] Applied to !UserFolder!
                        ) else (
                            echo   [SKIP] Could not load hive for !UserFolder! (may be in use)
                        )
                    )
                )
            )
        )
    )
)

echo.
echo [3/3] Applying policy to Default user profile (for new users)...
echo.

set "DefaultHive=C:\Users\Default\NTUSER.DAT"

if exist "%DefaultHive%" (
    REM Load the Default user hive
    reg load "HKU\DefaultUser" "%DefaultHive%" >nul 2>&1
    
    if !errorLevel! equ 0 (
        REM Create the policy key
        reg add "HKU\DefaultUser\%RegKeyPath%" /f >nul 2>&1
        
        REM Set the policy value
        reg add "HKU\DefaultUser\%RegKeyPath%" /v "%ValueName%" /t REG_DWORD /d %ValueData% /f >nul 2>&1
        
        if !errorLevel! equ 0 (
            echo   [OK] Applied to Default user profile
        ) else (
            echo   [WARN] Could not set value in Default profile
        )
        
        REM Unload the hive
        reg unload "HKU\DefaultUser" >nul 2>&1
    ) else (
        echo   [WARN] Could not load Default user hive
    )
) else (
    echo   [WARN] Default user hive not found at %DefaultHive%
)

echo.
echo ============================================================
echo  Operation Completed Successfully
echo ============================================================
echo.
echo IMPORTANT:
echo   - Microsoft Store will be UNPINNED on next user sign-in
echo   - Users currently logged in must sign out and back in
echo   - New users will not have Store pinned by default
echo   - The Microsoft Store app itself remains installed
echo.
echo Policy Applied:
echo   Registry: HKCU\Software\Policies\Microsoft\Windows\Explorer
echo   Value:    NoPinningStoreToTaskbar = 1
echo.

REM ============================================================================
REM CLEANUP SECTION - NASA-style defensive programming
REM ============================================================================

REM Ensure no registry hives are left loaded (defensive cleanup)
reg unload "HKU\DefaultUser" >nul 2>&1
for /d %%U in (C:\Users\*) do (
    reg unload "HKU\TempHive_%%~nxU" >nul 2>&1
)

REM Clear all script variables
set "RegKeyPath="
set "ValueName="
set "ValueData="
set "UserHive="
set "UserKeyPath="
set "UserFolder="
set "NtUserDat="
set "DefaultHive="

REM End the local environment scope (closes setlocal enabledelayedexpansion)
endlocal

:: ------------------------------------------------------------------------------
:: # CREATE DESTKOP ICONS

REM This batch file runs PowerShell commands to show desktop icons

powershell.exe -ExecutionPolicy Bypass -Command ^
"$icons = @{ ^
    '{20D04FE0-3AEA-1069-A2D8-08002B30309D}' = 0; ^
    '{59031a47-3f72-44a7-89c5-5595fe6b30ee}' = 0; ^
    '{208D2C60-3AEA-1069-A2D7-08002B30309D}' = 0; ^
    '{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}' = 0; ^
    '{645FF040-5081-101B-9F08-00AA002F954E}' = 0 ^
}; ^
$path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel'; ^
foreach ($guid in $icons.Keys) { ^
    New-ItemProperty -Path $path -Name $guid -PropertyType DWORD -Value $icons[$guid] -Force ^
}; ^
$shell = New-Object -ComObject Shell.Application; ^
$shell.Namespace(0).Self.InvokeVerb('Refresh')"

echo Desktop icons have been set to show.
pause 

:: ------------------------------------------------------------------------------

:: # SET BROWSER STARTUP
setlocal EnableExtensions

REM ============================================================
REM Golden Image - Set Startup Pages for Edge, Chrome, Firefox
REM Target URL: https://brookdalecc.edu
REM Run as Administrator
REM ============================================================

set "URL=https://brookdalecc.edu"

echo.
echo ============================================================
echo Setting browser startup policies to: %URL%
echo ============================================================
echo.

REM ------------------------------------------------------------
REM EDGE (Chromium) - Machine policy
REM RestoreOnStartup:
REM   4 = Open a list of URLs
REM RestoreOnStartupURLs = URLs to open at startup (REG_MULTI_SZ)
REM ------------------------------------------------------------
echo [EDGE] Writing HKLM policy keys...
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /f >nul 2>&1
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v RestoreOnStartup /t REG_DWORD /d 4 /f >nul
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v RestoreOnStartupURLs /t REG_MULTI_SZ /d "%URL%" /f >nul

REM Optional: also set homepage (doesn't hurt)
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v HomepageLocation /t REG_SZ /d "%URL%" /f >nul
reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v HomepageIsNewTabPage /t REG_DWORD /d 0 /f >nul

REM ------------------------------------------------------------
REM CHROME - Machine policy
REM RestoreOnStartup:
REM   4 = Open a list of URLs
REM RestoreOnStartupURLs = URLs to open at startup (REG_MULTI_SZ)
REM ------------------------------------------------------------
echo [CHROME] Writing HKLM policy keys...
reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /f >nul 2>&1
reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v RestoreOnStartup /t REG_DWORD /d 4 /f >nul
reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v RestoreOnStartupURLs /t REG_MULTI_SZ /d "%URL%" /f >nul

REM Optional: also set homepage
reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v HomepageLocation /t REG_SZ /d "%URL%" /f >nul
reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v HomepageIsNewTabPage /t REG_DWORD /d 0 /f >nul

REM ------------------------------------------------------------
REM FIREFOX - policies.json (system-wide)
REM Location:
REM   C:\Program Files\Mozilla Firefox\distribution\policies.json
REM If Firefox isn't installed in that path, we attempt (x86) too.
REM ------------------------------------------------------------
echo [FIREFOX] Writing policies.json...

set "FFROOT="
if exist "%ProgramFiles%\Mozilla Firefox\firefox.exe" set "FFROOT=%ProgramFiles%\Mozilla Firefox"
if not defined FFROOT if exist "%ProgramFiles(x86)%\Mozilla Firefox\firefox.exe" set "FFROOT=%ProgramFiles(x86)%\Mozilla Firefox"

if not defined FFROOT (
    echo   Firefox not found in Program Files. Skipping Firefox.
    goto :verify
)

set "FFDIST=%FFROOT%\distribution"
set "FFPOL=%FFDIST%\policies.json"

if not exist "%FFDIST%" mkdir "%FFDIST%" >nul 2>&1

REM Overwrite policies.json each time (good for monthly rebuilds)
(
echo {
echo   "policies": {
echo     "Homepage": {
echo       "URL": "%URL%",
echo       "StartPage": "homepage"
echo     },
echo     "RestoreOnStartup": {
echo       "Enabled": true,
echo       "URLs": [
echo         "%URL%"
echo       ]
echo     }
echo   }
echo }
) > "%FFPOL%"

:verify
echo.
echo ============================================================
echo Done.
echo Verification (optional):
echo   Edge:    edge://policy
echo   Chrome:  chrome://policy
echo   Firefox: about:policies
echo ============================================================
echo.

endlocal
:: ------------------------------------------------------------------------------


:: # REMOVE BLOATWARE APPS

powershell.exe -ExecutionPolicy Bypass -File "%~dp0Remove-Bloatware.ps1"

:: ------------------------------------------------------------------------------

ECHO SCRIPT COMPLETE
pause

7.2 Remove-Bloatware.ps1 (Cleanup Script)

#Requires -RunAsAdministrator
<#
.SYNOPSIS
    Enterprise Golden Image Bloatware Removal Script
.DESCRIPTION
    Removes Microsoft bloatware apps, Teams, OneDrive, and cleans up
    associated files, registry entries, and startup items.
.NOTES
    Run as Administrator
    For use in golden image preparation
#>

$ErrorActionPreference = 'SilentlyContinue'
$ProgressPreference = 'SilentlyContinue'

Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host "  Enterprise Bloatware Removal Script" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""

# ============================================================================
# SECTION 1: Remove AppX Packages (Current User)
# ============================================================================
Write-Host "[1/9] Removing AppX packages (current user)..." -ForegroundColor Yellow

$AppxPackages = @(
    "*Microsoft.BingNews*"
    "*Microsoft.Todos*"
    "*Microsoft.WindowsCommunicationsApps*"      # Mail and Calendar
    "*Microsoft.OutlookForWindows*"              # New Outlook
    "*Microsoft.Office.Desktop*"
    "*Microsoft.MicrosoftOfficeHub*"             # Office stub/hub
    "*MicrosoftTeams*"                           # Teams consumer
    "*MSTeams*"                                  # Teams 2.0
    "*Microsoft.SkypeApp*"
    "*Microsoft.People*"
    "*Microsoft.WindowsMaps*"
    "*Microsoft.ZuneMusic*"                      # Groove Music
    "*Microsoft.ZuneVideo*"                      # Movies & TV
    "*Microsoft.MicrosoftSolitaireCollection*"
    "*Microsoft.Xbox*"
    "*Microsoft.GamingApp*"
    "*Microsoft.GetHelp*"
    "*Microsoft.Getstarted*"                     # Tips
    "*Clipchamp*"
    "*Microsoft.549981C3F5F10*"                  # Cortana
)

foreach ($App in $AppxPackages) {
    $Package = Get-AppxPackage -Name $App -ErrorAction SilentlyContinue
    if ($Package) {
        Write-Host "  Removing: $($Package.Name)" -ForegroundColor Gray
        $Package | Remove-AppxPackage -ErrorAction SilentlyContinue
    }
}
Write-Host "  [OK] AppX packages removed" -ForegroundColor Green

# ============================================================================
# SECTION 2: Remove Provisioned AppX Packages (All Users / New Users)
# ============================================================================
Write-Host "[2/9] Removing provisioned packages (affects new users)..." -ForegroundColor Yellow

foreach ($App in $AppxPackages) {
    $Provisioned = Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue |
                   Where-Object { $_.PackageName -like $App }
    foreach ($Pkg in $Provisioned) {
        Write-Host "  Deprovisioning: $($Pkg.DisplayName)" -ForegroundColor Gray
        Remove-AppxProvisionedPackage -Online -PackageName $Pkg.PackageName -ErrorAction SilentlyContinue | Out-Null
    }
}
Write-Host "  [OK] Provisioned packages removed" -ForegroundColor Green

# ============================================================================
# SECTION 3: Remove Microsoft Teams (All Versions)
# ============================================================================
Write-Host "[3/9] Removing Microsoft Teams (all versions)..." -ForegroundColor Yellow

# Teams Machine-Wide Installer (MSI)
Write-Host "  Checking for Teams Machine-Wide Installer..." -ForegroundColor Gray
$TeamsMWI = Get-WmiObject -Class Win32_Product -ErrorAction SilentlyContinue |
            Where-Object { $_.Name -like "*Teams Machine-Wide*" }
if ($TeamsMWI) {
    Write-Host "  Uninstalling Teams Machine-Wide Installer..." -ForegroundColor Gray
    $TeamsMWI.Uninstall() | Out-Null
}

# Alternative: Use registry uninstall string
$UninstallPaths = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
    "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
)

foreach ($Path in $UninstallPaths) {
    $TeamsUninstall = Get-ItemProperty $Path -ErrorAction SilentlyContinue |
                      Where-Object { $_.DisplayName -like "*Teams Machine-Wide*" }
    if ($TeamsUninstall.UninstallString) {
        Write-Host "  Running uninstaller from registry..." -ForegroundColor Gray
        $UninstallCmd = $TeamsUninstall.UninstallString -replace '/I', '/X'
        Start-Process "msiexec.exe" -ArgumentList "$UninstallCmd /qn /norestart" -Wait -ErrorAction SilentlyContinue
    }
}

# Teams Meeting Add-in for Outlook
$TeamsAddin = Get-WmiObject -Class Win32_Product -ErrorAction SilentlyContinue |
              Where-Object { $_.Name -like "*Teams Meeting*" }
if ($TeamsAddin) {
    Write-Host "  Removing Teams Meeting Add-in..." -ForegroundColor Gray
    $TeamsAddin.Uninstall() | Out-Null
}

Write-Host "  [OK] Teams uninstall commands executed" -ForegroundColor Green

# ============================================================================
# SECTION 4: OneDrive (KEPT - runs on startup as intended)
# ============================================================================
Write-Host "[4/9] OneDrive..." -ForegroundColor Yellow
Write-Host "  [SKIP] OneDrive is being kept and will run on startup" -ForegroundColor Green

# ============================================================================
# SECTION 5: Clean Up Installation Directories
# ============================================================================
Write-Host "[5/9] Cleaning up installation directories..." -ForegroundColor Yellow

$DirsToRemove = @(
    "$env:ProgramFiles\Microsoft Office"
    "$env:ProgramFiles\Microsoft Office 15"
    "$env:ProgramFiles (x86)\Microsoft Office"
    "$env:ProgramFiles (x86)\Microsoft Office 15"
    "$env:ProgramFiles\WindowsApps\MSTeams*"
    "$env:ProgramFiles (x86)\Teams Installer"
    "$env:ProgramFiles (x86)\Microsoft\Teams"
    "$env:ProgramData\Microsoft\Teams"
    "$env:LOCALAPPDATA\Microsoft\Teams"
    "$env:LOCALAPPDATA\Microsoft\TeamsMeetingAddin"
    # OneDrive directories kept intentionally
)

foreach ($Dir in $DirsToRemove) {
    # Handle wildcard paths
    $ResolvedPaths = Resolve-Path $Dir -ErrorAction SilentlyContinue
    foreach ($ResolvedPath in $ResolvedPaths) {
        if (Test-Path $ResolvedPath) {
            Write-Host "  Removing: $ResolvedPath" -ForegroundColor Gray
            Remove-Item -Path $ResolvedPath -Recurse -Force -ErrorAction SilentlyContinue
        }
    }
}

Write-Host "  [OK] Installation directories cleaned" -ForegroundColor Green

# ============================================================================
# SECTION 6: Clean Up User Profile Folders (All Users)
# ============================================================================
Write-Host "[6/9] Cleaning up user profile folders..." -ForegroundColor Yellow

$UserProfiles = Get-ChildItem "C:\Users" -Directory -ErrorAction SilentlyContinue |
                Where-Object { $_.Name -notin @('Public', 'Default', 'Default User', 'All Users') }

$UserFoldersToClean = @(
    "AppData\Local\Microsoft\Teams"
    "AppData\Local\Microsoft\TeamsMeetingAddin"
    "AppData\Local\Microsoft\Outlook"
    "AppData\Roaming\Microsoft\Teams"
    "AppData\Roaming\Microsoft Teams"
    "AppData\Local\Packages\MSTeams*"
    "AppData\Local\Packages\Microsoft.OutlookForWindows*"
    "AppData\Local\Packages\microsoft.windowscommunicationsapps*"
    "AppData\Local\Packages\Microsoft.Todos*"
    "AppData\Local\Packages\Microsoft.BingNews*"
    "AppData\Local\Packages\Microsoft.MicrosoftOfficeHub*"
    # OneDrive folders kept intentionally
)

foreach ($UserProfileDir in $UserProfiles) {
    if ($null -eq $UserProfileDir) { continue }
    foreach ($Folder in $UserFoldersToClean) {
        $FullPath = Join-Path $UserProfileDir.FullName $Folder
        $ResolvedPaths = Resolve-Path $FullPath -ErrorAction SilentlyContinue
        foreach ($ResolvedPath in $ResolvedPaths) {
            if (Test-Path $ResolvedPath) {
                Write-Host "  Removing: $ResolvedPath" -ForegroundColor Gray
                Remove-Item -Path $ResolvedPath -Recurse -Force -ErrorAction SilentlyContinue
            }
        }
    }
}

Write-Host "  [OK] User profile folders cleaned" -ForegroundColor Green

# ============================================================================
# SECTION 7: Clean Up Registry Entries
# ============================================================================
Write-Host "[7/9] Cleaning up registry entries..." -ForegroundColor Yellow

# Startup entries (HKLM)
$StartupKeys = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
    "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run"
)

$StartupValuesToRemove = @(
    # OneDrive startup entries kept intentionally
    "Teams"
    "TeamsMachineInstaller"
    "TeamsMachineUninstallerLocalAppData"
)

foreach ($Key in $StartupKeys) {
    if (Test-Path $Key) {
        foreach ($Value in $StartupValuesToRemove) {
            Remove-ItemProperty -Path $Key -Name $Value -ErrorAction SilentlyContinue
        }
    }
}

# Remove Teams-related scheduled tasks (OneDrive tasks kept)
Write-Host "  Removing scheduled tasks..." -ForegroundColor Gray
Get-ScheduledTask -TaskName "*Teams*" -ErrorAction SilentlyContinue |
    Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue

# Remove Teams COM registration
$COMPaths = @(
    "HKLM:\SOFTWARE\Classes\CLSID"
    "HKLM:\SOFTWARE\Classes\TypeLib"
)

foreach ($COMPath in $COMPaths) {
    if (Test-Path $COMPath) {
        Get-ChildItem -Path $COMPath -ErrorAction SilentlyContinue |
            Where-Object { $_.Name -like "*Teams*" } |
            ForEach-Object {
                Remove-Item -Path $_.PSPath -Recurse -Force -ErrorAction SilentlyContinue
            }
    }
}

# OneDrive Shell Extensions - kept intentionally

Write-Host "  [OK] Registry entries cleaned" -ForegroundColor Green

# ============================================================================
# SECTION 8: Clean Up Default User Profile (For New Users)
# ============================================================================
Write-Host "[8/9] Cleaning up Default user profile..." -ForegroundColor Yellow

$DefaultUserFolders = @(
    "C:\Users\Default\AppData\Local\Microsoft\Teams"
    "C:\Users\Default\AppData\Roaming\Microsoft\Teams"
    "C:\Users\Default\AppData\Local\Packages\MSTeams*"
    "C:\Users\Default\AppData\Local\Packages\Microsoft.OutlookForWindows*"
    # OneDrive folders kept intentionally
)

foreach ($Folder in $DefaultUserFolders) {
    $ResolvedPaths = Resolve-Path $Folder -ErrorAction SilentlyContinue
    foreach ($ResolvedPath in $ResolvedPaths) {
        if (Test-Path $ResolvedPath) {
            Write-Host "  Removing: $ResolvedPath" -ForegroundColor Gray
            Remove-Item -Path $ResolvedPath -Recurse -Force -ErrorAction SilentlyContinue
        }
    }
}

# Load Default user registry hive and clean startup entries
$DefaultNTUser = "C:\Users\Default\NTUSER.DAT"
if (Test-Path $DefaultNTUser) {
    Write-Host "  Cleaning Default user registry hive..." -ForegroundColor Gray
    reg load "HKU\DefaultUser" $DefaultNTUser 2>$null

    $DefaultRunKey = "Registry::HKU\DefaultUser\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
    if (Test-Path $DefaultRunKey) {
        foreach ($Value in $StartupValuesToRemove) {
            Remove-ItemProperty -Path $DefaultRunKey -Name $Value -ErrorAction SilentlyContinue
        }
    }

    # Give it a moment then unload
    Start-Sleep -Seconds 1
    [gc]::Collect()
    reg unload "HKU\DefaultUser" 2>$null
}

Write-Host "  [OK] Default user profile cleaned" -ForegroundColor Green

# ============================================================================
# SECTION 9: Disable Copilot Auto-Start (Keep App Available)
# ============================================================================
Write-Host "[9/9] Disabling Copilot auto-start (keeping app available)..." -ForegroundColor Yellow

# Disable Copilot from running at startup via Group Policy (HKLM)
# This does NOT remove Copilot - it just prevents automatic startup
$CopilotPolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot"
if (!(Test-Path $CopilotPolicyPath)) {
    New-Item -Path $CopilotPolicyPath -Force | Out-Null
}
# Set policy to disable Copilot auto-launch but NOT disable the feature entirely
Set-ItemProperty -Path $CopilotPolicyPath -Name "TurnOffWindowsCopilot" -Value 0 -Type DWord -Force -ErrorAction SilentlyContinue
Write-Host "  Policy: Copilot remains available but won't auto-start" -ForegroundColor Gray

# Remove Copilot from startup locations (HKLM)
$CopilotStartupKeys = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
    "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run"
)

$CopilotStartupValues = @(
    "Copilot"
    "WindowsCopilot"
    "Microsoft Copilot"
    "MicrosoftCopilot"
)

foreach ($Key in $CopilotStartupKeys) {
    if (Test-Path $Key) {
        foreach ($Value in $CopilotStartupValues) {
            Remove-ItemProperty -Path $Key -Name $Value -ErrorAction SilentlyContinue
        }
        # Also check for any value containing "Copilot"
        $props = Get-ItemProperty -Path $Key -ErrorAction SilentlyContinue
        if ($props) {
            $props.PSObject.Properties | Where-Object { $_.Name -like "*Copilot*" } | ForEach-Object {
                Write-Host "  Removing startup entry: $($_.Name)" -ForegroundColor Gray
                Remove-ItemProperty -Path $Key -Name $_.Name -ErrorAction SilentlyContinue
            }
        }
    }
}

# Disable Copilot scheduled tasks (but don't delete them)
Write-Host "  Disabling Copilot scheduled tasks..." -ForegroundColor Gray
Get-ScheduledTask -TaskName "*Copilot*" -ErrorAction SilentlyContinue |
    Disable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null

# Also check for Microsoft 365 Copilot tasks
Get-ScheduledTask -TaskPath "*Microsoft*" -ErrorAction SilentlyContinue |
    Where-Object { $_.TaskName -like "*Copilot*" } |
    Disable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null

# Load Default user registry hive and disable Copilot startup for new users
if (Test-Path $DefaultNTUser) {
    Write-Host "  Disabling Copilot startup in Default user profile..." -ForegroundColor Gray
    reg load "HKU\DefaultUserCopilot" $DefaultNTUser 2>$null

    $DefaultUserRunKey = "Registry::HKU\DefaultUserCopilot\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
    if (Test-Path $DefaultUserRunKey) {
        foreach ($Value in $CopilotStartupValues) {
            Remove-ItemProperty -Path $DefaultUserRunKey -Name $Value -ErrorAction SilentlyContinue
        }
        # Remove any Copilot-related entries
        $props = Get-ItemProperty -Path $DefaultUserRunKey -ErrorAction SilentlyContinue
        if ($props) {
            $props.PSObject.Properties | Where-Object { $_.Name -like "*Copilot*" } | ForEach-Object {
                Remove-ItemProperty -Path $DefaultUserRunKey -Name $_.Name -ErrorAction SilentlyContinue
            }
        }
    }

    # Disable Copilot button on taskbar for cleaner experience (optional - users can re-enable)
    $DefaultUserAdvanced = "Registry::HKU\DefaultUserCopilot\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
    if (!(Test-Path $DefaultUserAdvanced)) {
        New-Item -Path $DefaultUserAdvanced -Force | Out-Null
    }
    Set-ItemProperty -Path $DefaultUserAdvanced -Name "ShowCopilotButton" -Value 0 -Type DWord -Force -ErrorAction SilentlyContinue

    Start-Sleep -Seconds 1
    [gc]::Collect()
    reg unload "HKU\DefaultUserCopilot" 2>$null
}

# Apply to currently logged-in users via HKCU policy
$CurrentUserCopilotPolicy = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
if (Test-Path $CurrentUserCopilotPolicy) {
    Set-ItemProperty -Path $CurrentUserCopilotPolicy -Name "ShowCopilotButton" -Value 0 -Type DWord -Force -ErrorAction SilentlyContinue
}

# Remove Copilot from current user startup
$CurrentUserRun = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
if (Test-Path $CurrentUserRun) {
    foreach ($Value in $CopilotStartupValues) {
        Remove-ItemProperty -Path $CurrentUserRun -Name $Value -ErrorAction SilentlyContinue
    }
    $props = Get-ItemProperty -Path $CurrentUserRun -ErrorAction SilentlyContinue
    if ($props) {
        $props.PSObject.Properties | Where-Object { $_.Name -like "*Copilot*" } | ForEach-Object {
            Write-Host "  Removing user startup entry: $($_.Name)" -ForegroundColor Gray
            Remove-ItemProperty -Path $CurrentUserRun -Name $_.Name -ErrorAction SilentlyContinue
        }
    }
}

Write-Host "  [OK] Copilot auto-start disabled (app remains available)" -ForegroundColor Green

# ============================================================================
# SUMMARY
# ============================================================================
Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host "  Bloatware Removal Complete" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "Removed:" -ForegroundColor White
Write-Host "  - Microsoft Teams (all versions)" -ForegroundColor Gray
Write-Host "  - Mail and Calendar" -ForegroundColor Gray
Write-Host "  - Outlook (new)" -ForegroundColor Gray
Write-Host "  - Microsoft News" -ForegroundColor Gray
Write-Host "  - Microsoft To Do" -ForegroundColor Gray
Write-Host "  - Office Hub" -ForegroundColor Gray
Write-Host "  - Xbox/Gaming apps" -ForegroundColor Gray
Write-Host "  - Other bloatware" -ForegroundColor Gray
Write-Host ""
Write-Host "Cleaned:" -ForegroundColor White
Write-Host "  - Installation directories" -ForegroundColor Gray
Write-Host "  - User data folders" -ForegroundColor Gray
Write-Host "  - Startup entries" -ForegroundColor Gray
Write-Host "  - Registry entries" -ForegroundColor Gray
Write-Host "  - Scheduled tasks" -ForegroundColor Gray
Write-Host "  - Default user profile" -ForegroundColor Gray
Write-Host ""
Write-Host "Disabled (still available):" -ForegroundColor White
Write-Host "  - Copilot auto-start" -ForegroundColor Gray
Write-Host ""
Write-Host "Kept (runs on startup):" -ForegroundColor White
Write-Host "  - OneDrive" -ForegroundColor Gray
Write-Host ""
Write-Host "NOTE: A reboot is recommended to complete cleanup." -ForegroundColor Yellow
Write-Host ""