Fixing the Black Screen After Sysprep and Cloudbase-Init Password Injection Issues on Windows Server 2025

When preparing a Windows Server 2025 template for VMware vSphere or Aria Automation, you may encounter two separate problems after running Sysprep:

  • The cloned virtual machine starts, but the desktop remains black and Windows Explorer does not load.
  • The Administrator password selected during deployment is not applied by Cloudbase-Init.

This article explains the working solutions for both issues and shows how to integrate them into a reusable Windows Server template.

Problem 1: Black Screen and Explorer Crash After Sysprep

After deploying a clone from a sysprepped Windows Server 2025 template, Windows may start successfully, but the user sees a black screen instead of the desktop.

Windows Explorer may crash with an error similar to:

Faulting application name: Explorer.EXE
Exception code: 0xc0000409

In this case, some Windows Shell and XAML system packages are not registered correctly for the logged-in user after Sysprep. The affected components can be repaired by registering their AppX manifests again.

Create the Shell Repair Script

Create the following directory:

C:\ProgramData\ShellRepair

Create this PowerShell script:

C:\ProgramData\ShellRepair\Repair-Shell.ps1

Use the following content:

$ErrorActionPreference = "Stop"

$markerPath = "HKCU:\Software\Company\ShellRepair"
$logDirectory = "C:\ProgramData\ShellRepair\Logs"
$logFile = Join-Path $logDirectory "$env:USERNAME.log"

New-Item -Path $logDirectory -ItemType Directory -Force | Out-Null

function Write-Log {
    param(
        [Parameter(Mandatory)]
        [string]$Message
    )

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    Add-Content -Path $logFile -Value "$timestamp $Message"
}

try {
    $completed = Get-ItemPropertyValue `
        -Path $markerPath `
        -Name "Completed" `
        -ErrorAction SilentlyContinue

    if ($completed -eq 1) {
        Write-Log "Shell repair already completed for this user."
        exit 0
    }

    Write-Log "Starting shell repair."

    $manifests = @(
        "C:\Windows\SystemApps\MicrosoftWindows.Client.CBS_cw5n1h2txyewy\AppxManifest.xml",
        "C:\Windows\SystemApps\Microsoft.UI.Xaml.CBS_8wekyb3d8bbwe\AppxManifest.xml",
        "C:\Windows\SystemApps\MicrosoftWindows.Client.Core_cw5n1h2txyewy\AppxManifest.xml"
    )

    foreach ($manifest in $manifests) {
        if (Test-Path $manifest) {
            Add-AppxPackage `
                -Register `
                -Path $manifest `
                -DisableDevelopmentMode

            Write-Log "Registered $manifest"
        }
        else {
            Write-Log "Manifest not found: $manifest"
        }
    }

    New-Item -Path $markerPath -Force | Out-Null

    New-ItemProperty `
        -Path $markerPath `
        -Name "Completed" `
        -Value 1 `
        -PropertyType DWord `
        -Force | Out-Null

    Stop-Process -Name sihost -Force -ErrorAction SilentlyContinue
    Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
    Start-Process explorer.exe

    Write-Log "Shell repair completed successfully."
}
catch {
    Write-Log "Shell repair failed: $($_.Exception.Message)"
    exit 1
}

The script registers the required Windows Shell packages and restarts the Shell Infrastructure Host and Windows Explorer.

A registry marker is created for each user:

HKCU\Software\Company\ShellRepair
Completed = 1

This prevents the repair process from running repeatedly for the same user.

Per-user logs are stored in:

C:\ProgramData\ShellRepair\Logs\<username>.log

Create the Scheduled Task

Run the following PowerShell script as Administrator on the template:

$taskName = "Repair Windows Shell at User Logon"
$scriptPath = "C:\ProgramData\ShellRepair\Repair-Shell.ps1"

$action = New-ScheduledTaskAction `
    -Execute "powershell.exe" `
    -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$scriptPath`""

$trigger = New-ScheduledTaskTrigger -AtLogOn

$principal = New-ScheduledTaskPrincipal `
    -GroupId "BUILTIN\Users" `
    -RunLevel Limited

$settings = New-ScheduledTaskSettingsSet `
    -AllowStartIfOnBatteries `
    -DontStopIfGoingOnBatteries `
    -ExecutionTimeLimit (New-TimeSpan -Minutes 5)

Register-ScheduledTask `
    -TaskName $taskName `
    -Action $action `
    -Trigger $trigger `
    -Principal $principal `
    -Settings $settings `
    -Description "Registers required Windows shell packages once for each user after Sysprep." `
    -Force

The task starts at every user logon, but the repair itself runs only once per user because of the registry marker.

Problem 2: Cloudbase-Init Does Not Apply the Administrator Password

The second issue occurs when Cloudbase-Init does not apply the password supplied by VMware or Aria Automation.

The Cloudbase-Init log may contain the following message:

Plugin 'SetUserPasswordPlugin' execution already done, skipping

This means that Cloudbase-Init believes the password plugin has already run for the current instance. As a result, the password configuration is skipped.

Cause of the Problem

Cloudbase-Init stores plugin execution state in the Windows Registry under:

HKLM\SOFTWARE\Cloudbase Solutions\Cloudbase-Init\<instance-id>

When the OVF environment does not provide a unique instance ID, Cloudbase-Init may use the default value:

iid-ovf

The stored plugin state can then be found under:

HKLM\SOFTWARE\Cloudbase Solutions\Cloudbase-Init\iid-ovf\Plugins

If this registry state remains inside the source template, every clone may be identified as the same Cloudbase-Init instance. Cloudbase-Init then skips plugins that were already marked as completed, including the password plugin.

Remove Cloudbase-Init Instance State Before Sysprep

Before running the final Sysprep operation, stop the Cloudbase-Init service and delete all stored instance state:

Stop-Service cloudbase-init -Force -ErrorAction SilentlyContinue

$statePath = "HKLM:\SOFTWARE\Cloudbase Solutions\Cloudbase-Init"

if (Test-Path $statePath) {
    Get-ChildItem -Path $statePath |
    Where-Object {
        $_.PSChildName -like "iid-*"
    } |
    Remove-Item -Recurse -Force
}

This removes instance-specific state such as:

HKLM\SOFTWARE\Cloudbase Solutions\Cloudbase-Init\iid-ovf

It does not remove the Cloudbase-Init installation or its configuration files.

Cloudbase-Init Main Configuration

The main configuration file is:

C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf

Important configuration options include:

[DEFAULT]
username=Administrator
groups=Administrators
inject_user_password=true
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
verbose=true
debug=true
metadata_services=cloudbaseinit.metadata.services.ovfservice.OvfService
plugins=cloudbaseinit.plugins.windows.createuser.CreateUserPlugin,cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin,cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin,cloudbaseinit.plugins.common.userdata.UserDataPlugin

The important settings are:

  • inject_user_password=true
  • cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin
  • cloudbaseinit.metadata.services.ovfservice.OvfService

Final Template Preparation Script

The following script stops Cloudbase-Init, removes saved instance state and starts Sysprep:

$ErrorActionPreference = "Stop"

$cloudbaseService = "cloudbase-init"
$statePath = "HKLM:\SOFTWARE\Cloudbase Solutions\Cloudbase-Init"
$sysprepPath = "C:\Windows\System32\Sysprep\Sysprep.exe"

try {
    Stop-Service `
        -Name $cloudbaseService `
        -Force `
        -ErrorAction SilentlyContinue

    if (Test-Path $statePath) {
        $instanceKeys = Get-ChildItem -Path $statePath |
            Where-Object {
                $_.PSChildName -like "iid-*"
            }

        foreach ($instanceKey in $instanceKeys) {
            Remove-Item `
                -Path $instanceKey.PSPath `
                -Recurse `
                -Force
        }
    }

    if (-not (Test-Path $sysprepPath)) {
        throw "Sysprep executable was not found."
    }

    Start-Process `
        -FilePath $sysprepPath `
        -ArgumentList "/generalize", "/oobe", "/shutdown", "/mode:vm" `
        -Wait
}
catch {
    Write-Error $_.Exception.Message
    exit 1
}

Save the script as:

C:\ProgramData\Prepare-CloudbaseTemplate.ps1

Run it as Administrator:

powershell.exe `
    -NoProfile `
    -ExecutionPolicy Bypass `
    -File "C:\ProgramData\Prepare-CloudbaseTemplate.ps1"

Sysprep Command

The script runs the following Sysprep command:

C:\Windows\System32\Sysprep\Sysprep.exe `
    /generalize `
    /oobe `
    /shutdown `
    /mode:vm

The parameters perform the following actions:

  • /generalize removes machine-specific information and prepares Windows for cloning.
  • /oobe starts the cloned VM in the Windows out-of-box preparation phase.
  • /shutdown shuts down the source VM after Sysprep completes.
  • /mode:vm optimizes Sysprep for deployment to similar virtual hardware on the same hypervisor.

Important Template Rule

After Sysprep shuts down the virtual machine, do not power on the source VM again.

Booting the source VM may start Cloudbase-Init and recreate the iid-ovf registry state. Convert the powered-off VM directly to a VMware template or use it as the source for cloning.

Recommended Workflow

  1. Complete the Windows Server, VMware Tools, application and security configuration.
  2. Install and configure Cloudbase-Init.
  3. Create the Shell Repair PowerShell script.
  4. Create the Shell Repair Scheduled Task.
  5. Stop the Cloudbase-Init service.
  6. Delete all Cloudbase-Init registry keys matching iid-*.
  7. Run Sysprep with /generalize /oobe /shutdown /mode:vm.
  8. Do not power on the source VM after Sysprep.
  9. Convert the powered-off VM to a VMware template.
  10. Deploy a new VM through VMware vSphere or Aria Automation.
  11. Cloudbase-Init applies the supplied Administrator password.
  12. The Shell Repair task runs at the first user logon and repairs Windows Explorer if required.

Conclusion

The Windows Server 2025 black screen issue can be resolved by re-registering the required Windows Shell and XAML packages for each user.

The Cloudbase-Init password issue is resolved by deleting the saved iid-* instance state before running the final Sysprep operation. This prevents cloned virtual machines from inheriting completed plugin state from the template.

Using both fixes provides a repeatable Windows Server 2025 template workflow for VMware vSphere and Aria Automation.

Fixing the Black Screen After Sysprep and Cloudbase-Init Password Injection Issues on Windows Server 2025

When preparing a Windows Server 2025 template for VMware vSphere or Aria Automation, you may encounter two separate problems after running Sysprep:

  • The cloned virtual machine starts, but the desktop remains black and Windows Explorer does not load.
  • The Administrator password selected during deployment is not applied by Cloudbase-Init.

This article explains the working solutions for both issues and shows how to integrate them into a reusable Windows Server template.

Problem 1: Black Screen and Explorer Crash After Sysprep

After deploying a clone from a sysprepped Windows Server 2025 template, Windows may start successfully, but the user sees a black screen instead of the desktop.

Windows Explorer may crash with an error similar to:

Faulting application name: Explorer.EXE
Exception code: 0xc0000409

In this case, some Windows Shell and XAML system packages are not registered correctly for the logged-in user after Sysprep. The affected components can be repaired by registering their AppX manifests again.

Create the Shell Repair Script

Create the following directory:

C:\ProgramData\ShellRepair

Create this PowerShell script:

C:\ProgramData\ShellRepair\Repair-Shell.ps1

Use the following content:

$ErrorActionPreference = "Stop"

$markerPath = "HKCU:\Software\Company\ShellRepair"
$logDirectory = "C:\ProgramData\ShellRepair\Logs"
$logFile = Join-Path $logDirectory "$env:USERNAME.log"

New-Item -Path $logDirectory -ItemType Directory -Force | Out-Null

function Write-Log {
    param(
        [Parameter(Mandatory)]
        [string]$Message
    )

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    Add-Content -Path $logFile -Value "$timestamp $Message"
}

try {
    $completed = Get-ItemPropertyValue `
        -Path $markerPath `
        -Name "Completed" `
        -ErrorAction SilentlyContinue

    if ($completed -eq 1) {
        Write-Log "Shell repair already completed for this user."
        exit 0
    }

    Write-Log "Starting shell repair."

    $manifests = @(
        "C:\Windows\SystemApps\MicrosoftWindows.Client.CBS_cw5n1h2txyewy\AppxManifest.xml",
        "C:\Windows\SystemApps\Microsoft.UI.Xaml.CBS_8wekyb3d8bbwe\AppxManifest.xml",
        "C:\Windows\SystemApps\MicrosoftWindows.Client.Core_cw5n1h2txyewy\AppxManifest.xml"
    )

    foreach ($manifest in $manifests) {
        if (Test-Path $manifest) {
            Add-AppxPackage `
                -Register `
                -Path $manifest `
                -DisableDevelopmentMode

            Write-Log "Registered $manifest"
        }
        else {
            Write-Log "Manifest not found: $manifest"
        }
    }

    New-Item -Path $markerPath -Force | Out-Null

    New-ItemProperty `
        -Path $markerPath `
        -Name "Completed" `
        -Value 1 `
        -PropertyType DWord `
        -Force | Out-Null

    Stop-Process -Name sihost -Force -ErrorAction SilentlyContinue
    Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
    Start-Process explorer.exe

    Write-Log "Shell repair completed successfully."
}
catch {
    Write-Log "Shell repair failed: $($_.Exception.Message)"
    exit 1
}

The script registers the required Windows Shell packages and restarts the Shell Infrastructure Host and Windows Explorer.

A registry marker is created for each user:

HKCU\Software\Company\ShellRepair
Completed = 1

This prevents the repair process from running repeatedly for the same user.

Per-user logs are stored in:

C:\ProgramData\ShellRepair\Logs\<username>.log

Create the Scheduled Task

Run the following PowerShell script as Administrator on the template:

$taskName = "Repair Windows Shell at User Logon"
$scriptPath = "C:\ProgramData\ShellRepair\Repair-Shell.ps1"

$action = New-ScheduledTaskAction `
    -Execute "powershell.exe" `
    -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$scriptPath`""

$trigger = New-ScheduledTaskTrigger -AtLogOn

$principal = New-ScheduledTaskPrincipal `
    -GroupId "BUILTIN\Users" `
    -RunLevel Limited

$settings = New-ScheduledTaskSettingsSet `
    -AllowStartIfOnBatteries `
    -DontStopIfGoingOnBatteries `
    -ExecutionTimeLimit (New-TimeSpan -Minutes 5)

Register-ScheduledTask `
    -TaskName $taskName `
    -Action $action `
    -Trigger $trigger `
    -Principal $principal `
    -Settings $settings `
    -Description "Registers required Windows shell packages once for each user after Sysprep." `
    -Force

The task starts at every user logon, but the repair itself runs only once per user because of the registry marker.

Problem 2: Cloudbase-Init Does Not Apply the Administrator Password

The second issue occurs when Cloudbase-Init does not apply the password supplied by VMware or Aria Automation.

The Cloudbase-Init log may contain the following message:

Plugin 'SetUserPasswordPlugin' execution already done, skipping

This means that Cloudbase-Init believes the password plugin has already run for the current instance. As a result, the password configuration is skipped.

Cause of the Problem

Cloudbase-Init stores plugin execution state in the Windows Registry under:

HKLM\SOFTWARE\Cloudbase Solutions\Cloudbase-Init\<instance-id>

When the OVF environment does not provide a unique instance ID, Cloudbase-Init may use the default value:

iid-ovf

The stored plugin state can then be found under:

HKLM\SOFTWARE\Cloudbase Solutions\Cloudbase-Init\iid-ovf\Plugins

If this registry state remains inside the source template, every clone may be identified as the same Cloudbase-Init instance. Cloudbase-Init then skips plugins that were already marked as completed, including the password plugin.

Remove Cloudbase-Init Instance State Before Sysprep

Before running the final Sysprep operation, stop the Cloudbase-Init service and delete all stored instance state:

Stop-Service cloudbase-init -Force -ErrorAction SilentlyContinue

$statePath = "HKLM:\SOFTWARE\Cloudbase Solutions\Cloudbase-Init"

if (Test-Path $statePath) {
    Get-ChildItem -Path $statePath |
    Where-Object {
        $_.PSChildName -like "iid-*"
    } |
    Remove-Item -Recurse -Force
}

This removes instance-specific state such as:

HKLM\SOFTWARE\Cloudbase Solutions\Cloudbase-Init\iid-ovf

It does not remove the Cloudbase-Init installation or its configuration files.

Cloudbase-Init Main Configuration

The main configuration file is:

C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf

Important configuration options include:

[DEFAULT]
username=Administrator
groups=Administrators
inject_user_password=true
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
verbose=true
debug=true
metadata_services=cloudbaseinit.metadata.services.ovfservice.OvfService
plugins=cloudbaseinit.plugins.windows.createuser.CreateUserPlugin,cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin,cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin,cloudbaseinit.plugins.common.userdata.UserDataPlugin

The important settings are:

  • inject_user_password=true
  • cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin
  • cloudbaseinit.metadata.services.ovfservice.OvfService

Final Template Preparation Script

The following script stops Cloudbase-Init, removes saved instance state and starts Sysprep:

$ErrorActionPreference = "Stop"

$cloudbaseService = "cloudbase-init"
$statePath = "HKLM:\SOFTWARE\Cloudbase Solutions\Cloudbase-Init"
$sysprepPath = "C:\Windows\System32\Sysprep\Sysprep.exe"

try {
    Stop-Service `
        -Name $cloudbaseService `
        -Force `
        -ErrorAction SilentlyContinue

    if (Test-Path $statePath) {
        $instanceKeys = Get-ChildItem -Path $statePath |
            Where-Object {
                $_.PSChildName -like "iid-*"
            }

        foreach ($instanceKey in $instanceKeys) {
            Remove-Item `
                -Path $instanceKey.PSPath `
                -Recurse `
                -Force
        }
    }

    if (-not (Test-Path $sysprepPath)) {
        throw "Sysprep executable was not found."
    }

    Start-Process `
        -FilePath $sysprepPath `
        -ArgumentList "/generalize", "/oobe", "/shutdown", "/mode:vm" `
        -Wait
}
catch {
    Write-Error $_.Exception.Message
    exit 1
}

Save the script as:

C:\ProgramData\Prepare-CloudbaseTemplate.ps1

Run it as Administrator:

powershell.exe `
    -NoProfile `
    -ExecutionPolicy Bypass `
    -File "C:\ProgramData\Prepare-CloudbaseTemplate.ps1"

Sysprep Command

The script runs the following Sysprep command:

C:\Windows\System32\Sysprep\Sysprep.exe `
    /generalize `
    /oobe `
    /shutdown `
    /mode:vm

The parameters perform the following actions:

  • /generalize removes machine-specific information and prepares Windows for cloning.
  • /oobe starts the cloned VM in the Windows out-of-box preparation phase.
  • /shutdown shuts down the source VM after Sysprep completes.
  • /mode:vm optimizes Sysprep for deployment to similar virtual hardware on the same hypervisor.

Important Template Rule

After Sysprep shuts down the virtual machine, do not power on the source VM again.

Booting the source VM may start Cloudbase-Init and recreate the iid-ovf registry state. Convert the powered-off VM directly to a VMware template or use it as the source for cloning.

Recommended Workflow

  1. Complete the Windows Server, VMware Tools, application and security configuration.
  2. Install and configure Cloudbase-Init.
  3. Create the Shell Repair PowerShell script.
  4. Create the Shell Repair Scheduled Task.
  5. Stop the Cloudbase-Init service.
  6. Delete all Cloudbase-Init registry keys matching iid-*.
  7. Run Sysprep with /generalize /oobe /shutdown /mode:vm.
  8. Do not power on the source VM after Sysprep.
  9. Convert the powered-off VM to a VMware template.
  10. Deploy a new VM through VMware vSphere or Aria Automation.
  11. Cloudbase-Init applies the supplied Administrator password.
  12. The Shell Repair task runs at the first user logon and repairs Windows Explorer if required.

Conclusion

The Windows Server 2025 black screen issue can be resolved by re-registering the required Windows Shell and XAML packages for each user.

The Cloudbase-Init password issue is resolved by deleting the saved iid-* instance state before running the final Sysprep operation. This prevents cloned virtual machines from inheriting completed plugin state from the template.

Using both fixes provides a repeatable Windows Server 2025 template workflow for VMware vSphere and Aria Automation.

Leave a Reply

Your email address will not be published. Required fields are marked *