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
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.ps1to 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.ps1and 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:
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:
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:
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:
And its output:
Notice it starts out in full language mode but once inside $jobScript it is constrained
Error details
Environment data
Visuals
No response