Skip to content

"RegisterMicrosoftUpdate.ps1" fails and freezes installer when AppLocker / WDAC rules exist (despite the script being whitelisted & running full language mode) #23850

Description

@ThioJoe

Prerequisites

Steps to reproduce

Situation Where This Occurs: When a system is set up with any security/execution policy that causes PowerShell to run in Constrained Language mode by default, such as the PowerShell Core Group Policy Editor execution policy being set as "Allow only signed scripts", or AppLocker rules that require whitelisting to run in Full Language mode.

Problem: If the user runs the installer and selects to use Microsoft Update (I believe, or otherwise whatever causes RegisterMicrosoftUpdate.ps1 to be run as part of the installation), and the system is set up as described above, the installer will hang at the end forever showing no error and never complete.

Root Cause: The problem exists in RegisterMicrosoftUpdate.ps1 and the fact that the $jobScript block is run as a new thread which creates a new session outside of the whitelisted session, therefore it is in Constrained Language Mode instead of the outer script's Full Language Mode, so it fails. Then for some reason the installer doesn't notice this and just hangs instead of displaying an error and moving on.


More Problem Details

I noticed the PowerShell 7 MSI installer would freeze near the end and show no movement or message. I ran the MSI installer with verbose logging and it was hanging completely with the last line being this:

CAQuietExec:  "C:\Program Files\PowerShell\7\pwsh.exe" -NoProfile -ExecutionPolicy Bypass -File "C:\Program Files\PowerShell\7\RegisterMicrosoftUpdate.ps1"

To be clear, the script should theoretically be able to run no problem, because in AppLocker I have rules to allow any signed scripts, as well as the powershell core policy in GPE being set to allow only signed scripts (which it is). I also even tried explicitly adding an allow rule on the script's file hash and temporarily changing the group policy GPE setting, but it didn't work (because AppLocker was still active, therefore defaulting to Constrained Language), but given the cause, this is not a surprise. The script itself is running in Full Language mode, that's not the problem, rather the inner code block mentioned before.

Running the script by itself yields these errors:

PS C:\Program Files\PowerShell\7> .\RegisterMicrosoftUpdate.ps1
VERBOSE: Running job script:
            # This registers Microsoft Update via a predifened GUID with the Windows Update Agent.
            # https://learn.microsoft.com/windows/win32/wua_sdk/opt-in-to-microsoft-update

            $serviceManager = (New-Object -ComObject Microsoft.Update.ServiceManager)
            $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu

            if (!$isRegistered) {
                Write-Verbose -Verbose "Opting into Microsoft Update as the Autmatic Update Service"
                # 7 is the combination of asfAllowPendingRegistration, asfAllowOnlineRegistration, asfRegisterServiceWithAU
                # AU means Automatic Updates
                $null = $serviceManager.AddService2('7971f918-a847-4430-9279-4a52d1efe18d', 7, '')
            }
            else {
                Write-Verbose -Verbose "Microsoft Update is already registered for Automatic Updates"
            }

            $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu

            # Return if it was successful, which is the opposite of Pending.
            return $isRegistered

VERBOSE: Waiting on Job for 300 seconds
VERBOSE: Job finished.  State: Completed
New-Object:
Line |
   5 |  … viceManager = (New-Object -ComObject Microsoft.Update.ServiceManager)
     |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Cannot create type. Only core types are supported in this language mode.
InvalidOperation:
Line |
   6 |              $isRegistered = $serviceManager.QueryServiceRegistration( …
     |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | You cannot call a method on a null-valued expression.
InvalidOperation:
Line |
  12 |  …             $null = $serviceManager.AddService2('7971f918-a847-4430-9 …
     |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | You cannot call a method on a null-valued expression.
InvalidOperation:
Line |
  18 |              $isRegistered = $serviceManager.QueryServiceRegistration( …
     |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | You cannot call a method on a null-valued expression.
VERBOSE: Opting into Microsoft Update as the Autmatic Update Service
VERBOSE: Result:
VERBOSE: Registration failed

With the main problem being:
Cannot create type. Only core types are supported in this language mode.

The problem is like I described above, how $scriptBlock is being run in a new session/context so it doesn't inherit the Full Language mode and fails despite being within the allowed script.

Expected behavior

I would expect the installer to display an error message that the registering to Windows Update had failed but the installation was otherwise a success.

Example Solution:

With the help of ChatGPT I had it create a version of the script that runs the script block in the same context and works:

param(
    [ValidateSet('Hang', 'Fail')]
    $TestHook
)

$waitTimeoutSeconds = 300
switch ($TestHook) {
    'Hang' {
        $waitTimeoutSeconds = 10
        $jobScript = { Start-Sleep -Seconds 600 }
    }
    'Fail' {
        $jobScript = { throw "This job script should fail" }
    }
    default {
        $jobScript = {
            # This registers Microsoft Update via a predifened GUID with the Windows Update Agent.
            # https://learn.microsoft.com/windows/win32/wua_sdk/opt-in-to-microsoft-update

            $serviceManager = (New-Object -ComObject Microsoft.Update.ServiceManager)
            $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu

            if (!$isRegistered) {
                Write-Verbose -Verbose "Opting into Microsoft Update as the Automatic Update Service"
                # 7 is the combination of asfAllowPendingRegistration, asfAllowOnlineRegistration, asfRegisterServiceWithAU
                # AU means Automatic Updates
                $null = $serviceManager.AddService2('7971f918-a847-4430-9279-4a52d1efe18d', 7, '')
            }
            else {
                Write-Verbose -Verbose "Microsoft Update is already registered for Automatic Updates"
            }

            $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu

            # Return if it was successful, which is the opposite of Pending.
            return $isRegistered
        }
    }
}

Write-Verbose "Running job script" -Verbose

# Run the script block synchronously in the current session
$result = & $jobScript

Write-Verbose "Result: $result" -Verbose
if ($result) {
    Write-Verbose "Registration succeeded" -Verbose
    exit 0
}
else {
    Write-Verbose "Registration failed" -Verbose
    # at the time this was written, the MSI is ignoring the exit code
    exit 1
}

Actual behavior

Installer hangs forever until force quit with Task manager.

I added logging lines to various parts of the script to see the language mode at different parts which revealed that part of it was running in constrained language mode.

Here is the modified script:

param(
    [ValidateSet('Hang', 'Fail')]
    $TestHook
)

Write-Output "1 The execution policy is: $($ExecutionContext.SessionState.LanguageMode)`n"

$waitTimeoutSeconds = 300
switch ($TestHook) {
    'Hang' {
        $waitTimeoutSeconds = 10
        $jobScript = { Start-Sleep -Seconds 600 }
    }
    'Fail' {
        $jobScript = { throw "This job script should fail" }
    }
    default {
        $jobScript = {
            # This registers Microsoft Update via a predifened GUID with the Windows Update Agent.
            # https://learn.microsoft.com/windows/win32/wua_sdk/opt-in-to-microsoft-update
			Write-Output "2 The execution policy is: $($ExecutionContext.SessionState.LanguageMode)`n"
            $serviceManager = (New-Object -ComObject Microsoft.Update.ServiceManager)
            $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu
			Write-Output "3 The execution policy is: $($ExecutionContext.SessionState.LanguageMode)`n"
            if (!$isRegistered) {
				Write-Output "4 The execution policy is: $($ExecutionContext.SessionState.LanguageMode)`n"
                Write-Verbose -Verbose "Opting into Microsoft Update as the Autmatic Update Service"
                # 7 is the combination of asfAllowPendingRegistration, asfAllowOnlineRegistration, asfRegisterServiceWithAU
                # AU means Automatic Updates
                $null = $serviceManager.AddService2('7971f918-a847-4430-9279-4a52d1efe18d', 7, '')
				Write-Output "5 The execution policy is: $($ExecutionContext.SessionState.LanguageMode)`n"
            }
            else {
                Write-Verbose -Verbose "Microsoft Update is already registered for Automatic Updates"
            }

            $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu
			Write-Output "6 The execution policy is: $($ExecutionContext.SessionState.LanguageMode)`n"
            # Return if it was successful, which is the opposite of Pending.
            return $isRegistered
        }
    }
}

Write-Verbose "Running job script: $jobScript" -Verbose
$job = Start-ThreadJob -ScriptBlock $jobScript

Write-Verbose "Waiting on Job for $waitTimeoutSeconds seconds" -Verbose
$null = Wait-Job -Job $job -Timeout $waitTimeoutSeconds

if ($job.State -ne 'Running') {
    Write-Verbose "Job finished.  State: $($job.State)" -Verbose
    $result = Receive-Job -Job $job -Verbose
    Write-Verbose "Result: $result" -Verbose
    if ($result) {
        Write-Verbose "Registration succeeded" -Verbose
        exit 0
    }
    else {
        Write-Verbose "Registration failed" -Verbose
        # at the time this was written, the MSI is ignoring the exit code
        exit 1
    }
}
else {
    Write-Verbose "Job timed out" -Verbose
    Write-Verbose "Stopping Job.  State: $($job.State)" -Verbose
    Stop-Job -Job $job
    # at the time this was written, the MSI is ignoring the exit code
    exit 258
}

And its output:

PS C:\Users\Joe\Desktop\Tests\Powershell bug> .\RegisterMicrosoftUpdate.ps1
1 The execution policy is: FullLanguage


[[[ Removed redundant verbose lines ]]]

New-Object:
Line |
   5 |  … viceManager = (New-Object -ComObject Microsoft.Update.ServiceManager)
     |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Cannot create type. Only core types are supported in this language mode.
InvalidOperation:
Line |
   6 |              $isRegistered = $serviceManager.QueryServiceRegistration( …
     |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | You cannot call a method on a null-valued expression.
InvalidOperation:
Line |
  13 |  …             $null = $serviceManager.AddService2('7971f918-a847-4430-9 …
     |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | You cannot call a method on a null-valued expression.
InvalidOperation:
Line |
  20 |              $isRegistered = $serviceManager.QueryServiceRegistration( …
     |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | You cannot call a method on a null-valued expression.
VERBOSE: Opting into Microsoft Update as the Autmatic Update Service
VERBOSE: Result: 2 The execution policy is: ConstrainedLanguage
 3 The execution policy is: ConstrainedLanguage 4 The execution policy is: ConstrainedLanguage
 5 The execution policy is: ConstrainedLanguage
 6 The execution policy is: ConstrainedLanguage

VERBOSE: Registration succeeded

Notice it starts out in full language mode but once inside $jobScript it is constrained

Error details

New-Object:
Line |
   5 |  … viceManager = (New-Object -ComObject Microsoft.Update.ServiceManager)
     |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Cannot create type. Only core types are supported in this language mode.

Environment data

Name                           Value
----                           -----
PSVersion                      7.4.2
PSEdition                      Core
GitCommitId                    7.4.2
OS                             Microsoft Windows 10.0.26100
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs-TriageThe issue is new and needs to be triaged by a work group.Review - MaintainerThe PR/issue needs a review from the PowerShell repo MaintainersWG-Maintainers-Buildspecific to affecting the buildWG-ReviewedA Working Group has reviewed this and made a recommendation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions